30th
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