Deep eager load on rails - ruby-on-rails

In this thread know how to make a eager load in rails, but how to do this nested?
I.e:
# get category, random product and random photo
#category = Category.find(params[:id], :include => random_product, :include => random_photo)
I don't know if I explain...
Thanks in advance.

You can eager load nested associations by giving a hash to the :include option:
#category = Category.find(params[:id], :include => { :random_product => :random_photo})

Related

Preload associations with an existing scope

I wrote a piece of code to eager load some associations from an already loaded collection:
#articles= Article.find_by_sql("SELECT * FROM articles WHERE blabla")
ActiveRecord::Associations::Preloader.new(#articles, {:comments => {:user => :permissions}}).run
I have a scope defined in my Article class ready to eager load some articles associations at several levels:
class Article << ActiveRecord::Base
[...]
scope :eager_loading_for_comments, includes(:comments => {:user => :permissions})
end
Am I able to use this scope in my first code ? A way like that:
ActiveRecord::Associations::Preloader.new(#articles, :eager_loading_for_comments).run
Or:
ActiveRecord::Associations::Preloader.new(#articles, Article.eager_loading_for_comments).run
Thank you !
Why not just call the scope as you would any other, e.g.
#articles = Article.eager_loading_for_comments
?
How about chaining the where onto the scope:
Article.eager_loading_for_comments.where("blah blah blah")

Eager loading and map in rails 3

If I wanted to eagerly load a collection in rails and render it in json, I would have to do something like this.
#photos = #event.photos.to_json(:include =>
{:appearances => {:include => :person}}
)
What if I wanted to map this collection? As you can see it's no longer a collection, but a json string. Prior to this necessary eager loading, I was doing the following:
#photos = #event.photos.map{|photo|
photo['some_funky_stuff'] = photo.funky_calculation
photo
}
But, I can't seem to be able to do the two things together:
#event.photos.map{|photo|
photo['some_funky_stuff'] = photo.funky_calculation
photo
}.to_json(:include =>
{:appearances => {:include => :person}}
)
The above does not show 'appearances' ( the eagerly loaded join record )... How do I do these two together? Many thanks!
You may have the term "eager loading" mixed up a little bit. As previous answers have mentioned, you need to use it on the association for it to be eager loaded. However, when you use :include in the to_json call, you will still end up with the same result, no matter if it is eager or not.
But to answer your question, for the to_json method to both include the appearances and the funky_calculation you can combine it with :methods instead. Try it like this:
#photos = #event.photos.to_json(
:include => {:appearances => {:include => :person}},
:methods => [: funky_calculation]
)
And if you want increased performance (eager loading), then use include on the associations as well:
#photos = #event.photos.includes(:appearances => :person).to_json(
:include => {:appearances => {:include => :person}},
:methods => [: funky_calculation]
)
You could eager load using includes after has_many association
#photos = #event.photos.includes(:appearances => [:person]).to_json
You might want to try using joins() or includes() on photos, instead as an option to to_json().
http://guides.rubyonrails.org/active_record_querying.html#using-array-hash-of-named-associations

Combined order with association in Rails 3

I have two models, post and comment. Posts has many comments and comments belongs to posts.
What I'm trying to do, is to have a list of posts that is ordered by creation date, unless it has comments. Then it needs to take the creation date of the latest comment.
Now I know that I can include associations like this:
Post.find(:all, :include => [:comments], :order => "comments.created_at DESC, posts.created_at DESC")
But this will order all posts with comments first and then the posts without comments. I don't want either ones ordered separately, but combined.
The solution also needs to be compatible with a paginate gem like will_paginate.
Edit
I have it now working with the following code:
posts_controller.rb
#posts = Post.order('updated_at DESC').page(params[:page]).per_page(10)
comments_controller.rb
#post = Post.find(params[:post_id])
#post.update_attributes!(:updated_at => Time.now)
I'd recommend:
Add a last_commented_at date field to your Post model. Whenever someone comments on the post update that field. It de-normalizes your db structure a bit, but your query will be faster and more strait-forward.
Postgres
Post.paginate(:include => [:comments],
:order => "COALESCE(comments.created_at, posts.created_at)",
:page => 1, :per_page => 10)
MySQL
Post.paginate(:include => [:comments],
:order => "IFNULL(comments.created_at, posts.created_at)",
:page => 1, :per_page => 10)

Rails child object query leads to N+1 problem

while writing some application for personal use. I find out the child query is not as great as it look.
For instance, I have 2 object
Category has_many Files
File belongs_to Category
File.category will access its parent category. But this lead to the famous N+1 problem.
For example, I want my homepage to list out 20 newest files and its corresponding category using something like this
for file in #files
%p
= file.name
= file.category.name
How should I solve this problem?
#files = File.find(:all, :limit => 20, :order => "created at desc", :include => :category)
In your find if you say :include => :category then this will eager load the categories for you and avoid a separate query to retrieve each category's name. So for your example of the 20 most recent files you could do:
#files = File.find :all, :limit => 20, :include => :category,
:order => 'created at desc'

Rails - Active Record :conditions overrides :select

I have a fairly large model and I want to retrieve only a select set of fields for each record in order to keep the JSON string I am building small.
Using :select with find works great but my key goal is to use conditional logic with an associated model. Is the only way to do this really with a lamda in a named scope? I'm dreading that perhaps unnecessarily but I'd like to understand if there is a way to make the :select work with a condition.
This works:
#sites = Site.find :all, :select => 'id,foo,bar'
When I try this:
#sites = Site.find :all, :select => 'id,foo,bar', :include => [:relatedmodel],
:conditions => ["relatedmodel.type in (?)", params[:filters]]
The condition works but each record includes all of the Site attributes which makes my JSON string way way too large.
Thanks for any pointers!
The to_json call supports :except and :only options to exclude/include model fields during serialization.
#sites.to_json(:only => [:name, :foo, :bar])
Call above serializes the Site objects with fields name and location.
#sites.to_json(:only => [:name, :location],
:include => { :relatedmodel => {
:only => [:description]
}
}
)
Call above serializes the Site objects with fields name, and location and contained RelatedModel objects with description field.

Resources