Skip to content
Yet Another N+1 COUNT Query Killer for ActiveRecord
Ruby Logos Shell
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
ci
lib
spec
.gitignore
.rspec
.travis.yml
CHANGELOG.md
Gemfile
LICENSE.txt
README.md
Rakefile
activerecord-precounter.gemspec

README.md

ActiveRecord::Precounter Build Status

Yet Another N+1 COUNT Query Killer for ActiveRecord, counter_cache alternative.
ActiveRecord::Precounter allows you to cache count of associated records by eager loading.

This is another version of activerecord-precount, which is not elegant but designed to have no monkey-patch to ActiveRecord internal APIs for maintainability.

Synopsis

N+1 count query

Sometimes you may see many count queries for one association. You can use counter_cache to solve it, but you need to ALTER table and concern about dead lock to use counter_cache.

tweets = Tweet.all
tweets.each do |tweet|
  p tweet.favorites.count
end
# SELECT `tweets`.* FROM `tweets`
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 1
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 2
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 3
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 4
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 5

Count eager loading

precount

With activerecord-precounter gem installed, you can use ActiveRecord::Precounter#precount method to eagerly load counts of associated records. Like preload, it loads counts by multiple queries

tweets = Tweet.all
ActiveRecord::Precounter.new(tweets).precount(:favorites)
tweets.each do |tweet|
  p tweet.favorites_count
end
# SELECT `tweets`.* FROM `tweets`
# SELECT COUNT(`favorites`.`tweet_id`), `favorites`.`tweet_id` FROM `favorites` WHERE `favorites`.`tweet_id` IN (1, 2, 3, 4, 5) GROUP BY `favorites`.`tweet_id`

Installation

Add this line to your application's Gemfile:

gem 'activerecord-precounter'

Limitation

Target has_many association must have inversed belongs_to. i.e. ActiveRecord::Precounter.new(tweets).precount(:favorites) needs both Tweet.has_many(:favorites) and Favorite.belongs_to(:tweet).

Unlike activerecord-precount, the cache store is not ActiveRecord association and it does not utilize ActiveRecord preloader. Thus you can't use preload to eager load counts for nested associations. And currently there's no JOIN support.

License

MIT License

You can’t perform that action at this time.