16
votes

Eager loading is nice with the include attribute

Post.find(:all, :include => :author)

I'm wondering if you can also eager load counts, like if I wanted to get the number of comments for each post, without loading all the comments themselves?

Maybe something like

Post.find(:all, :include => [:author, "count(comments)")

I suppose I could use a count_cache column. But doing it all in an include would be really beautiful, if it's possible.

Extra bonus points if someone can show how to not only get the count, but put some conditions too, like count of only posts which have been approved.

5
This was later expanded in this question: stackoverflow.com/questions/4908878/… Which is strongly related to: stackoverflow.com/questions/2870521/… (which is how I found it.)Tim Snowhite

5 Answers

31
votes

they should already be loaded use

post.comments.length

I was having this same problem because I was using .count

5
votes

Building off of avaynshtok's answer, the following technique should just make 2 database calls.

# ./app/controllers/posts_controller.rb

def index
  # First load the posts
  @posts = Post.all

  # Then you can load a hash of author counts grouped by post_id
  # Rails 4 version:
  @comment_counts = Comment.group(:post_id).count
  # Rails 3 version:
  # @comment_counts = Comment.count(:group => :post_id)
end

Then in your view

<!-- ./app/views/posts/index.html.erb  -->

<% @posts.each do |post| %>
  <!-- reference the count by the post.id -->
  post_count: <%= @comment_counts[post.id] %>
<% end %>
2
votes

Try this:

Comment.count(:group => :post)

To filter by conditions:

Comment.count(:group => :post, :conditions => {:approved => true })

These will return hashes with posts as keys and the number of comments as values.

0
votes

I just ran into this challenge and solved it this way:

def trainee_counts
   @trainee_counts ||= Hash[Trainee.group(:klass_id).count]
end

   # where the count is needed
   trainee_counts[klass_id].to_i

One call to database and does not load trainees.

-7
votes

In MySQL at least, it is faster to do these as two separate calls because you get to avoid the join. I know this doesn't answer your question, but it seems like you're trying to get better speed and doing

Post.find ...

Then

post.comments.count

Is faster and more memory efficient (for the database) than if you retrieve both in one query.