“...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

Counter cache is now read only?

I recently added counter cache to one of my assocations to try and solve a validation problem. ie. If an object has too many associated objects already then it would be invalid.

Easy, set up counter cache and update your existing database with the current totals, however this doesn’t work anymore. It doesn’t set them at all.

Subscription.all.each do |s| 
  s.update_attribute :requirements_count, s.requirements.length
end

I can see all the updates in my log, but minus the ‘requirements_count’ which was the whole point :S

Requirement Load (0.9ms)   SELECT * FROM "requirements" WHERE ("requirements".subscription_id = 158) ORDER BY created_on asc
  Subscription Update (0.9ms)   UPDATE "subscriptions" SET "updated_on" = '2009-07-11 17:44:35.463992' WHERE "id" = 158
  Requirement Load (1.1ms)   SELECT * FROM "requirements" WHERE ("requirements".subscription_id = 264) ORDER BY created_on asc
  Subscription Update (0.9ms)   UPDATE "subscriptions" SET "updated_on" = '2009-07-11 17:44:35.470008' WHERE "id" = 264

I found this in the ActiveRecord changelog :( It states this is now a readonly field. Under normal circumstances I agree it should be readonly but what about when you just want to get your data into the right state?

*2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.14.2 - 1.15.3]

* Add attr_readonly to specify columns that are skipped during a normal ActiveRecord #save operation. Closes #6896 [Dan Manges]

  class Comment < ActiveRecord::Base
    # Automatically sets Article#comments_count as readonly.
    belongs_to :article, :counter_cache => :comments_count
  end

  class Article < ActiveRecord::Base
    attr_readonly :approved_comments_count
  end

The solution it seems is to redeclare the class you are trying update the count in in the migration class. I cut and pasted this from a mailing list post I found. This seems stupidly awkward a solution and took far too long to diagnose this problem.

class AddRequirementsCountToSubscriptions < ActiveRecord::Migration
 
  class Subscription < ActiveRecord::Base

    has_many :requirements

    def reset_column_information
      generated_methods.each {|name| undef_method(name) }
      @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritence_column = nil
    end
      
  end

  def self.up
    add_column :subscriptions, :requirements_count, :integer, :default => 0
    
    Subscription.reset_column_information

    Subscription.all.each do |s|
      puts "UPDATING #{s.id}"
      puts "#{s.requirements.length}"
      s.update_attribute :requirements_count, s.requirements.length
    end
  end

  def self.down
    remove_column :subscriptions, :requirements_count
  end

end

sigh