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://code.alexreisner.com/articles/single-table-inheritance-in-rails.html" target="_blank">Single Table Inhertance</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/ 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.
Comments