44

I'm getting 'rake aborted! ... posts_count is marked readonly' errors.

I have two models: user and post.

users has_many posts.

posts belongs_to :user, :counter_cache => true

I have a migration which adds the posts_count column to the users table and then calculates and records the current number of posts per user.

self.up
  add_column :users, :posts_count, :integer, :default => 0

  User.reset_column_information
  User.all.each do |u|
    u.update_attribute( :posts_count, u.posts.count)
  end
end

when I run the migration I get the error. This is pretty clear-cut, of course and if I remove the :counter_cache declaration from the posts model, e.g.

belongs_to :user

the migration runs fine. This obviously, does not make sense because you couldn't really implement it this way. What am I missing?

NJ.
  • 2,155
  • 6
  • 26
  • 35

2 Answers2

98

You should be using User.reset_counters to do this. Additionally, I would recommend using find_each instead of each because it will iterate the collection in batches instead of all at once.

self.up
  add_column :users, :posts_count, :integer, :default => 0

  User.reset_column_information
  User.find_each do |u|
    User.reset_counters u.id, :posts
  end
end
Adam Lassek
  • 35,156
  • 14
  • 91
  • 107
  • 2
    It appears that the preferred approach is: "User.reset_counters u.id, :posts_count". See http://api.rubyonrails.org/classes/ActiveRecord/CounterCache.html – Mike Fischer Jan 21 '11 at 00:30
  • Slight correction to above (not able to edit that comment any more): "User.reset_counters u.id, :posts" – Mike Fischer Jan 21 '11 at 00:36
  • @Mike Fischer good catch, looks like `reset_counters` uses fewer SQL calls. – Adam Lassek Jan 21 '11 at 02:37
  • 1
    @gwho I don't see any deprecation notice on either method in either `4-1-stable` or `master`. What makes you think that? https://github.com/rails/rails/blob/master/activerecord/lib/active_record/counter_cache.rb – Adam Lassek Sep 18 '14 at 04:16
  • apidock said that. And it wasn't working for me. i'm mistaken – ahnbizcad Sep 18 '14 at 18:16
  • 1
    @gwho apidock calls anything "deprecated" if the code moves locations. I generally avoid that site, it is often confusing like that. – Adam Lassek Sep 24 '14 at 02:29
3

OK, the documentation states:

Counter cache columns are added to the containing model’s list of read-only attributes through attr_readonly.

I think this is what happens: you declare the counter in the model's definition, thus rendering the "posts_count" attribute read-only. Then, in the migration, you attempt to update it directly, resulting in the error you mention.

The quick-and-dirty solution is to remove the counter_cache declaration from the model, run the migration (in order to add the required column to the database AND populate it with the current post counts), and then re-add the counter_cache declaration to the model. Should work but is nasty and requires manual intervention during the migration - not a good idea.

I found this blog post which suggests altering the model's list of read-only attributes during the migration, it's a bit oudated but you might want to give it a try.

Roadmaster
  • 5,297
  • 1
  • 23
  • 21
  • I appreciate your help. I am using the workaround you suggested, but as you agree, it is not a good way to get things into production. Thanks also for the blog article, but that hack doesn't seem to work anymore (I am using rails 3.0.1 currently.) I may just have to roll my own cache counters and maintain them in the app. – NJ. Nov 28 '10 at 05:34
  • 1
    That blog post is misleading or outdated. See [this one](http://josh.the-owens.com/rails-edge-change-how-to-add-a-counter-cache). – Adam Lassek Nov 28 '10 at 06:54
  • This was the problem for me. `counter_cache: true` was setting doing some behind the scene `attr_readonly` magic behind the scenes and blocking my migration – Meltemi Aug 27 '13 at 22:28
  • This way poses a problem when trying to do it on a remote repository like heroku. Upload a version with countercache off, then migrate, then upload a verson with countercache implemented? ew. – ahnbizcad Sep 29 '14 at 23:46