Build Status

Preload Counts

Did you ever write an index page that only needs the count of an association? Consider the following example:

# Models
class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post

  named_scope :by_moderators, lambda { {:conditions => {:by_moderator => true} }
end

# Controller
def index
  @posts = Post.all
end

# View
<% @posts.each do |post| %>
  <%= post.name %>
  (<%= post.comments.count %>)
  (<%= post.comments.by_moderators.count %>)
<% end %>

This will create two count request to the database for each post each time the view is rendered. This is really slow. Preload counts helps you minimize the number of calls to the database with minimal code change.

Usage

# Model
class Post < ActiveRecord::Base
  # create a named_scope to preload the comments count as well as the comments
  # by_moderators. 
  preload_counts :comments => [:by_moderators]

  # preload_counts :comments would have only, preloaded the comments which
  # would have been slightly faster. You can preload any number of scopes, but
  # the more to preload, the more complex and slow the request will be.
  has_many :comments
end

# Controller
def index
  # Tell AR to preload the counts in the select request. If you don't specify
  # this, the behaviour is to fallback to the slow count.
  @posts = Post.preload_comment_counts
end

# View
# You have different accessor to get to the count. Beware of methods like
# .empty? that has an internal call to count.
<% @posts.each do |post| %>
  <%= post.name %>
  (<%= post.comments_count %>)
  (<%= post.by_moderators_comments_count %>)
<% end %>

Benchmark

More in depth benchmarking is needed, but here's sone annecdotal evidence.

Without: 650ms per request With: 450ms per request

That's right, this saved me 200ms per request with very minimal code change.

Support

This has been tested on Rails 2.3.12, 3.1.0 and 4.0.2.

Help

My requests are still slow?

It's possible that this isn't the fix you need. Sometimes multiple small requests might be faster than one large request. Always benchmark your page before using this gem.

Another thing you might want to look into is adding an index to speed up the query that is being generated by preload_counts.