“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it. Currently working for InPay...”

Rob Lacey (contact@robl.me)
Senior Software Engineer, Brighton, UK

Resetting counter cache

Ran into a problem whereby the counter cache on one of our associations was out of sync. Simple …just update the counter cache with the count of the association?

rails >> u User.find(1)
rails >> u.update_attribute(:roles_count, 1)
ActiveRecord::ActiveRecordError: roles_count is marked as readonly
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/activerecord-3.2.18/lib/active_record/persistence.rb:194:in `update_column'
	from (irb):17
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/railties-3.2.18/lib/rails/commands/console.rb:47:in `start'
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/railties-3.2.18/lib/rails/commands/console.rb:8:in `start'
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/railties-3.2.18/lib/rails/commands.rb:41:in `<top (required)>'
	from /data/app/current/script/rails:6:in `require'
	from /data/app/current/script/rails:6:in `<main>'
rails >> u.update_column(:roles_count, 1)
ActiveRecord::ActiveRecordError: roles_count is marked as readonly
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/activerecord-3.2.18/lib/active_record/persistence.rb:194:in `update_column'
	from (irb):17
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/railties-3.2.18/lib/rails/commands/console.rb:47:in `start'
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/railties-3.2.18/lib/rails/commands/console.rb:8:in `start'
	from /data/app/shared/bundled_gems/ruby/2.1.0/gems/railties-3.2.18/lib/rails/commands.rb:41:in `<top (required)>'
	from /data/app/current/script/rails:6:in `require'
	from /data/app/current/script/rails:6:in `<main>'
rails pro >> User.readonly_attributes
=> #<Set: {"roles_count"}>

Seems you can’t update the value, I was surprised that update_column wouldn’t work either. I guess it makes sense to protect this value since it’s supposed to be kept in sync and only in rare occasions should it be out of sync. However, you can use the reset_counters class method.

rails >> u = User.find(1)
rails pro >> User.reset_counters u.id, :roles
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
   (0.4ms)  SELECT COUNT(*) FROM `roles` INNER JOIN `role_users` ON `roles`.`id` = `role_users`.`role_id` WHERE `role_users`.`user_id` = 1
   (0.9ms)  UPDATE `users` SET `roles_count` = 1 WHERE `users`.`id` = 355411
=> true