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.
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
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`
Add this line to your application's Gemfile:
gem 'activerecord-precounter'
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.
MIT License