Recalculate counter cache columns in Rails

Rails has this great feature called counter_cache. When you have a has_many/belongs_to relationship between two models, it basically lets you say that you want to save the number of associated objects on the has_many side. So if you have Post has many Comments, you can do post#size and no SQL query is executed, Rails simply just looks at the comments_count field of the post.

And - as with so many things in Rails - the beauty is that it just works out of the box, the way you would expect it to work. When you create or delete Comments, Rails keeps the comments_count fields of all associated Posts up to date. Well, most of the time. I have had the situation a couple of times where I ended up with a negative value which obviously doesn't make sense.

So with every Rails project that reached "medium size" (whatever that meant at the time), I started worrying about data consistency and I created a service that is executed automatically regularly (e.g. by a Cron job) and checks things like counter_cache columns. And because Ruby contains lots of awesome meta-programming magic, you don't have to remember to check each counter_cache column explicity but let Ruby find them using reflection:

# Make sure to load all models first

ActiveRecord::Base.descendants.each do |many_class|
  many_class.reflections.each do |name, reflection|
    if reflection.options[:counter_cache]
      one_class = reflection.class_name.constantize
      one_table, many_table = [one_class, many_class].map(&:table_name)
      ids = one_class
        .having("#{one_table}.#{many_table}_count != COUNT(#{many_table}.id)")
      ids.each do |id|
        one_class.reset_counters id, many_table

Discuss this post on Hacker News

Ideas? Constructive criticism? Think I'm stupid? Let me know in the comments!