Preload association or named_scope counts for ActiveRecord
Switch branches/tags
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
lib Bump version to 0.0.4 Jan 6, 2015
spec Configurable version of active record Aug 16, 2014
.gitignore Extract preload_counts in a gem. Sep 18, 2011
.rspec WIP Rails 4.1 port Aug 15, 2014
Gemfile Added rake to gemfile Aug 16, 2014
Rakefile Removed dependencies on multi_rails May 28, 2013

Build Status

Preload Counts

Have you ever built an index page that displays the count of an association? Often this is a very expensive query, requiring database table joins. That's where preload_counts comes in: using this gem will simplify those lookups into a single optimized query.

Consider the following example:

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

class Comment < ActiveRecord::Base
  belongs_to :post

  scope :by_moderators, -> { where(by_moderator: true) }

# Controller
def index
  @posts = Post.all

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

This will create two SQL COUNT queries 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 a simple one-line code change.


# 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

# 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

# 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.comments_count %>)
  (<%= post.by_moderators_comments_count %>)
<% end %>


Using Bundler:

# Gemfile
gem 'preload_counts'

Manually from

% gem install preload_counts


More in depth benchmarking is needed, but here's some annecdotal evidence:

  • Without preload_counts: ~650ms per request
  • With preload_counts: ~450ms per request

That's right! This saved me 200ms per request, even with a very minimal code change.


This gem has been sanity tested on the following Rails versions:

  • 2.3.12
  • 3.1.0
  • 4.0.2
  • 4.2.1


Q. I'm using preload_counts, but my requests are still slow!

A. 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.