19

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.

Brian Armstrong
  • 19,707
  • 17
  • 115
  • 144
  • This was later expanded in this question: http://stackoverflow.com/questions/4908878/how-do-i-get-rails-to-eager-load-counts Which is strongly related to: http://stackoverflow.com/questions/2870521/how-will-activerelation-affect-rails-includes-s-capabilities (which is how I found it.) – Tim Snowhite Feb 27 '11 at 19:19

5 Answers5

35

they should already be loaded use

post.comments.length

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

Christopher
  • 3,391
  • 2
  • 21
  • 13
  • 4
    I don't think this is what he is looking for. This will load all the entries in the comments association and then count them. He wants to use SQL to eagerly pull the count without loading the objects. (Or at least that's how I understand it). – Mike Nov 24 '09 at 20:08
  • 1
    This was the solution I was looking for. On and index page of posts where I'd like to iterate over the collection and display the count of comments - this avoids firing a count request in each cycle (Provided the comments have been eager loaded together with the posts). Thanks :-) – Renra Jun 29 '14 at 08:40
7

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 %>
Vincent
  • 4,876
  • 3
  • 44
  • 55
Ryan
  • 9,340
  • 5
  • 39
  • 42
2

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.

avaynshtok
  • 2,519
  • 1
  • 16
  • 14
  • It's not exactly what I'm looking for. Yes, you can do it in a separate call like this. But I don't think you can do them in an association like I wanted. Probably have to use count_cache column. – Brian Armstrong Dec 03 '09 at 23:31
0

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.

korada
  • 576
  • 4
  • 10
-8

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.

Drew Blas
  • 3,028
  • 1
  • 21
  • 11