Last Updated: March 13, 2017
·
1.975K
· jorge-d

Easily keep track of rails' relationship count with counter_cache

Your rails server's logs is full of rows like this one:

(0.3ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 1

Or maybe you just want to store the number of children that a parent model holds to do some calculation queries

Setup

Setting up counter_cache is pretty straightforward. It just consists in adding a new field storing a counter value that will be updated every-time a member is added/removed to the collection using ActiveRecord's callbacks.

# app/models/post.rb
class Post < ActiveRecord::Base
    has_many :comments
end

# app/models/comment.rb
class Comment < ActiveRecord::Base
    belongs_to :post, counter_cache: true
end

Then we create the migration:

class AddCommentsCountToPosts < ActiveRecord::Migration
  def change
    add_column :posts, :comments_count, :integer, default: 0, null: false

    # Update the counter for existing records
    Post.select(:id) do |result|
      Post.reset_counters(result.id, :comments)
    end
  end
end

And that's all we need to do ! :)

Now we can easily do some queries like:

Post.where("created_at > ?", 2.month.ago).average(:comments_count)

Note 1: It also works with polymorphic associations (despite that it seems to need an additional setup

Note 2: the counter is only updated on model's creation and deletion. If you need to change something when it's updated so you will need a workaround like this one or you can use this gem

Credits: ElegantRuby, StackOverflow and Rails guide