Rails: How can I set default values in ActiveRecord?
There are several issues with each of the available methods, but I believe that defining an after_initialize
callback is the way to go for the following reasons:
default_scope
will initialize values for new models, but then that will become the scope on which you find the model. If you just want to initialize some numbers to 0 then this is not what you want.- Defining defaults in your migration also works part of the time... As has already been mentioned this will not work when you just call Model.new.
- Overriding
initialize
can work, but don't forget to callsuper
! - Using a plugin like phusion's is getting a bit ridiculous. This is ruby, do we really need a plugin just to initialize some default values?
- Overriding
after_initialize
is deprecated as of Rails 3. When I overrideafter_initialize
in rails 3.0.3 I get the following warning in the console:
DEPRECATION WARNING: Base#after_initialize has been deprecated, please use Base.after_initialize :method instead. (called from /Users/me/myapp/app/models/my_model:15)
Therefore I'd say write an after_initialize
callback, which lets you default attributes in addition to letting you set defaults on associations like so:
class Person < ActiveRecord::Base
has_one :address
after_initialize :init
def init
self.number ||= 0.0 #will set the default value only if it's nil
self.address ||= build_address #let's you set a default association
end
end
Now you have just one place to look for initialization of your models. I'm using this method until someone comes up with a better one.
Caveats:
For boolean fields do:
self.bool_field = true if self.bool_field.nil?
See Paul Russell's comment on this answer for more details
If you're only selecting a subset of columns for a model (ie; using
select
in a query likePerson.select(:firstname, :lastname).all
) you will get aMissingAttributeError
if yourinit
method accesses a column that hasn't been included in theselect
clause. You can guard against this case like so:self.number ||= 0.0 if self.has_attribute? :number
and for a boolean column...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
Also note that the syntax is different prior to Rails 3.2 (see Cliff Darling's comment below)
Rails 5+
You can use the attribute method within your models, eg.:
class Account < ApplicationRecord
attribute :locale, :string, default: 'en'
end
You can also pass a lambda to the default
parameter. Example:
attribute :uuid, :string, default: -> { SecureRandom.uuid }
The second argument is the type and it can also be a custom type class instance, for example:
attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
We put the default values in the database through migrations (by specifying the :default
option on each column definition) and let Active Record use these values to set the default for each attribute.
IMHO, this approach is aligned with the principles of AR : convention over configuration, DRY, the table definition drives the model, not the other way around.
Note that the defaults are still in the application (Ruby) code, though not in the model but in the migration(s).