Friday, 28 March 2008

Rails: Don't override initialize on ActiveRecord objects

ActiveRecord::Base doesn't always use new to create objects, so initialize might not be called. I wanted to use a Hash on an ActiveRecord::Base subclass to store some calculated values, so I naively did this:
class User < ActiveRecord::Base
  def initialize(args = nil)
    super
    @my_cache = {}
  end
end
However I quickly ran into some "You have a nil object when you didn't expect it!" issues. Some debugger investigation revealed that the @my_cache variable wasn't being set when I called find_or_create_ if the object already existed in the database. Digging in the source revealed that the instantiate method in active_record/base.rb uses allocate to create classes rather than new. This means the initialize method is being neatly sidestepped when creating objects from the database. The solution is to use the 'after_initialize' callback:
class User < ActiveRecord::Base
  def after_initialize
    @my_cache = {}
  end
end
One further note of caution, When passing parameters into a new or create method the after_initialize is called after the parameters have been set. So you can't rely on the initialization being done before overridden accessors are called.