sholsinger.com

Categories Photography Résumé About Me

Complex Default Values with Rails 3

05 Sep 2011

I needed an extensible way to default values for new models in Rails 3.0.5 so I went Googling. I ended up finding a great question on StackOverflow. (of course) So I got started with the help of the answers on that question.

I first tried the before_create callback but soon realized that it only fires when you save a new record. I needed the model (A) to auto-generate associated models (B) when a new instance of the model (A) is created. So the correct filter for that is after_initialize. I called my callback method set_default_values() and used it on my abstract super class (A) and the classes (C,etc) that inherit from it.

Here's an example:

# app/models/model_a.rb
class ModelA < ActiveRecord::Base

  self.abstract_class = true
  after_initialize :set_default_values
  has_one :model_b
  has_one :model_z

  # ModelC represents a ModelA with a specific ModelZ
  # Note: This _should_ be done with <a title="Single Table Inheritance in Rails" href="http:&#47;&#47;code.alexreisner.com&#47;articles&#47;single-table-inheritance-in-rails.html" target="_blank">Single Table Inhertance<&#47;a>

  # ...

  private def set_default_values
    if new_record?
      self.active ||= true
      self.model_b ||= ModelB.new
    end
  end

end

I'm not using the = operator for the assignments of the default values. This is due to the fact that initialize() sets the default values of the instance properties with the supplied hash. (eg: ModelA.new {:val1 => 'value'}) So we're allowing the defaults to be overridden by a hash passed into the constructor method. In addition I check to see if the record is a new record and only apply my defaults if it is. I don't want to override values on instances loaded with ModelA.find(1).

Now in ModelB we need to trigger the super class' set_default_values method in addition to adding our own defaults.

# app/models/model_c.rb
class ModelC < ModelA

  after_initialize :set_default_values

  # ...

  private def set_default_values
    super # call ModelA method w&#47; same name as the current method
    if new_record?
      self.model_z = ModelZ.find_by_name('Specific Model Z name')
    end
  end

end

The reason I use the = operator above is that ModelC represents a ModelA with a specific ModelZ relation so I don't want that value to be overridden for any reason. I'll say again that this sort of inheritance should not be done the way I am doing it here. It should be done with single table inheritance as described by Alex Reisner. You'll thank me later. I'm holding off on migrating my database to fit that model until after everything else is working. #ifitaintbrokedontfixituntillater

Note: As far as I know this technique should work for any version of Rails (ActiveRecord) that supports the after_initialize callback.

Filed under

Comments
comments powered by Disqus