How do I get Rails to eager load counts?

非 Y 不嫁゛ 提交于 2019-11-28 21:28:43

It appears that the best way to implement this sort of facility might be to create SQL Views (ref: here and here) for the seperate model-and-child-count objects that you want; and their associated ActiveRecord models.

You might be able to be very clever and use subclassing on the original model combined with set_table_name :sql_view_name to retain all the original methods on the objects, and maybe even some of their associations.

For instance, say we were to add 'Post.has_many :comments' to your example, like in @Zubin's answer above; then one might be able to do:

   class CreatePostsWithCommentsCountsView < ActiveRecord::Migration
      def self.up
        #Create SQL View called posts_with_comments_counts which maps over 
        # select posts.*, count(comments.id) as comments_count from posts 
        #   left outer join comments on comments.post_id = posts.id 
        #   group by posts.id
        # (As zubin pointed out above.) 
        #*Except* this is in SQL so perhaps we'll be able to do further 
        # reducing queries against it *as though it were any other table.*
      end    
   end

   class PostWithCommentsCount < Post         #Here there be cleverness.
                                              #The class definition sets up PWCC 
                                              # with all the regular methods of 
                                              # Post (pointing to the posts table
                                              # due to Rails' STI facility.)

    set_table_name :posts_with_comment_counts #But then we point it to the 
                                              # SQL view instead.
                                              #If you don't really care about
                                              # the methods of Post being in PWCC
                                              # then you could just make it a 
                                              # normal subclass of AR::Base.
   end

   PostWithCommentsCount.all(:include => :user)  #Obviously, this sort of "upward
     # looking" include is best used in big lists like "latest posts" rather than
     # "These posts for this user." But hopefully it illustrates the improved 
     # activerecordiness of this style of solution.
   PostWithCommentsCount.all(:include => :comments) #And I'm pretty sure you 
     # should be able to do this without issue as well. And it _should_ only be 
     # the two queries.

As @apneadiving suggested, counter_cache works well because the counter column gets automatically updated when records are added or removed. So when you load the parent object, the count is included in the object without needing to access the other table.

However, if for whatever reason you don't like that approach, you could do this:

Post.find(:all,
          :select => "posts.*, count(comments.id) `comments_count`",
          :joins  => "left join comments on comments.post_id = posts.id")

I have set up a small gem that adds an includes_count method to ActiveRecord, that uses a SELECT COUNT to fetch the number of records in an association, without resorting to a JOIN which might be expensive (depending on the case).

See https://github.com/manastech/includes-count

Hope it helps!

An alternative approach to the one of Zubin:

Post.select('posts.*, count(comments.id) `comments_count`').joins(:comments).group('posts.id')
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!