bitfluent

Kamal Fariz Mahyuddin on Ruby on Rails, Ember.js and other web development geekery.

You should follow me on twitter here.
Mar
30th
Sun
permalink

Updating Counter Cache in Migrations

I’m nearly complete with an app I’ve been working on, so I thought I’d dedicate some time for optimization. One of the first things I did to my models was to add counter caches so that performing counts on my association were fast.

First try,

class AddCommentCounterCacheOnTopics < ActiveRecord::Migration
  def self.up
    add_column :topics, :comments_count, :integer, :default => 0
  end

  def self.down
    remove_column :topics, :comments_count
  end
end

Very standard. However, it initializes the column to 0, effectively “losing” all my comments (they’re there in the DB, but Rails adds an additional optimization whereby it won’t fetch the association if the counter cache is 0). Looks like I need to update the comments_count column to whatever it was at the time of migration.

Second try,

class AddCommentCounterCacheOnTopics < ActiveRecord::Migration
  def self.up
    add_column :topics, :comments_count, :integer, :default => 0

    Topic.find(:all).each do |t|
      t.comments_count = t.comments.count
      t.save!
    end
  end

  def self.down
    remove_column :topics, :comments_count
  end
end

Two things to note: First, I’m using the #count method in t.comments.count because calling #size will use the value in t.comment_count which is 0. #count on the other hand will perform a SQL count(). Second, this won’t work! Why? Because counter cache columns are set to attr_readonly.

To work around this, a little hack. Final result,

class AddCommentCounterCacheOnTopics < ActiveRecord::Migration
  def self.up
    add_column :topics, :comments_count, :integer, :default => 0

    def Topic.readonly_attributes; nil end # A little evil hack

    Topic.find(:all).each do |t|
      t.comments_count = t.comments.count
      t.save!
    end
  end

  def self.down
    remove_column :topics, :comments_count
  end
end
Fork me on GitHub