Reputation: 318
I've been looking for the best method to do this quite some time with mediocre results so I decided to ask here.
The scenario is as follows: I have three models, Task, User and Comment, that essentially look like this:
class Task < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class User < ActiveRecord::Base
has_many :tasks
has_many :comments
end
I'm trying to output a list of tasks (let's say last 10 for the purpose of this question), and associated last comment for each task and it's author (user model) with the least queries possible.
Thank you
UPDATE: I've combined solutions by Blue Smith and harigopal so the solution looks like this:
class Task < ActiveRecord::Base
belongs_to :user
has_many :comments
has_one :last_comment, -> { order 'created_at' }, class_name: "Comment"
end
and then fetch comments like this:
tasks = Task.joins(last_comment: :user)
.includes(last_comment: :user)
.order('tasks.created_at DESC').limit(10).load
which produces only one query, which was exactly what I was looking for! Thank you!
Upvotes: 2
Views: 2969
Reputation: 8820
You can try this:
class Task < ActiveRecord::Base
belongs_to :user
has_many :comments
has_one :last_comment, :class_name => 'Comment', :order => 'comments.created_at DESC'
end
@tasks = Task.limit(10).includes(:last_comment => :user)
last_comment
is a "virtual" association and just load only one Comment
record. The advantage of this approach is that it will use less memory (because just load one Comment
and one related User
). If you use includes(comments: user)
it may consume a lot of memory (if there a lot of comments :).
Upvotes: 1
Reputation: 718
You'll have to join the tables, and eager-load the data to minimize number of queries.
For example:
tasks = Task.joins(comments: :user)
.includes(comments: :user)
.order('tasks.created_at DESC')
.limit(10).load
comments = tasks.first.comments # No query, eager-loaded
user = comments.first.user # No query, eager-loaded
This should reduce the number of queries to just one (very complex one), so you'll have to make sure your indexing is up to snuff! :-D
Official documentation about combining joins and includes is vague, but should help: http://guides.rubyonrails.org/active_record_querying.html#joining-multiple-associations
EDIT:
Here's a demo application with the same models as yours performing eager-loading using the above method. It uses two queries to load the data. Fire up the app to see it in action.
https://github.com/harigopal/activerecord-join-eager-loading
Upvotes: 2
Reputation: 2560
You could try something along the lines of this:
class Task
scope :recent, order('created_at desc')
scope :last, lambda{|n| limit: n }
end
Now you have reusable scopes:
Task.recent.last(10)
And i'm guessing you want to output the last 10 tasks for a given user (let's say the current logged in user).
current_user.tasks.recent.last(10).includes(comments: user)
Upvotes: 1