How to set default values in Rails?
Ruby on-RailsRubyRuby on-Rails Problem Overview
I'm trying to find the best way to set default values for objects in Rails.
The best I can think of is to set the default value in the new
method in the controller.
Does anyone have any input if this is acceptable or if there's a better way to do it?
Ruby on-Rails Solutions
Solution 1 - Ruby on-Rails
"Correct" is a dangerous word in Ruby. There's usually more than one way to do anything. If you know you'll always want that default value for that column on that table, setting them in a DB migration file is the easiest way:
class SetDefault < ActiveRecord::Migration
def self.up
change_column :people, :last_name, :type, :default => "Doe"
end
def self.down
# You can't currently remove default values in Rails
raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
end
end
Because ActiveRecord autodiscovers your table and column properties, this will cause the same default to be set in any model using it in any standard Rails app.
However, if you only want default values set in specific cases -- say, it's an inherited model that shares a table with some others -- then another elegant way is do it directly in your Rails code when the model object is created:
class GenericPerson < Person
def initialize(attributes=nil)
attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
super(attr_with_defaults)
end
end
Then, when you do a GenericPerson.new()
, it'll always trickle the "Doe" attribute up to Person.new()
unless you override it with something else.
Solution 2 - Ruby on-Rails
Based on SFEley's answer, here is an updated/fixed one for newer Rails versions:
class SetDefault < ActiveRecord::Migration
def change
change_column :table_name, :column_name, :type, default: "Your value"
end
end
Solution 3 - Ruby on-Rails
First of all you can't overload initialize(*args)
as it's not called in all cases.
Your best option is to put your defaults into your migration:
add_column :accounts, :max_users, :integer, :default => 10
Second best is to place defaults into your model but this will only work with attributes that are initially nil. You may have trouble as I did with boolean
columns:
def after_initialize
if new_record?
max_users ||= 10
end
end
You need the new_record?
so the defaults don't override values loaded from the datbase.
You need ||=
to stop Rails from overriding parameters passed into the initialize method.
Solution 4 - Ruby on-Rails
You can also try change_column_default
in your migrations (tested in Rails 3.2.8):
class SetDefault < ActiveRecord::Migration
def up
# Set default value
change_column_default :people, :last_name, "Smith"
end
def down
# Remove default
change_column_default :people, :last_name, nil
end
end
Solution 5 - Ruby on-Rails
If you are referring to ActiveRecord objects, you have (more than) two ways of doing this:
1. Use a :default parameter in the DB
E.G.
class AddSsl < ActiveRecord::Migration
def self.up
add_column :accounts, :ssl_enabled, :boolean, :default => true
end
def self.down
remove_column :accounts, :ssl_enabled
end
end
More info here: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
2. Use a callback
E.G. before_validation_on_create
More info here: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147
Solution 6 - Ruby on-Rails
In Ruby on Rails v3.2.8, using the after_initialize
ActiveRecord callback, you can call a method in your model that will assign the default values for a new object.
> after_initialize callback is triggered for each object that is found and instantiated by a finder, with after_initialize being triggered after new objects are instantiated as well (see ActiveRecord Callbacks).
So, IMO it should look something like:
class Foo < ActiveRecord::Base
after_initialize :assign_defaults_on_new_Foo
...
attr_accessible :bar
...
private
def assign_defaults_on_new_Foo
# required to check an attribute for existence to weed out existing records
self.bar = default_value unless self.attribute_whose_presence_has_been_validated
end
end
Foo.bar = default_value
for this instance unless the instance contains an attribute_whose_presence_has_been_validated
previously on save/update. The default_value
will then be used in conjunction with your view to render the form using the default_value
for the bar
attribute.
At best this is hacky...
EDIT - use 'new_record?' to check if instantiating from a new call
Instead of checking an attribute value, use the new_record?
built-in method with rails. So, the above example should look like:
class Foo < ActiveRecord::Base
after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
...
attr_accessible :bar
...
private
def assign_defaults_on_new_Foo
self.bar = default_value
end
end
This is much cleaner. Ah, the magic of Rails - it's smarter than me.
Solution 7 - Ruby on-Rails
In case you're dealing with a Model, you can use the Attriutes API in Rails 5+ http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute
just add a migration with a proper column name and then in the model set it with:
class StoreListing < ActiveRecord::Base
attribute :country, :string, default: 'PT'
end
Solution 8 - Ruby on-Rails
For boolean fields in Rails 3.2.6 at least, this will work in your migration.
def change
add_column :users, :eula_accepted, :boolean, default: false
end
Putting a 1
or 0
for a default will not work here, since it is a boolean field. It must be a true
or false
value.
Solution 9 - Ruby on-Rails
Generate a migration and use change_column_default
, is succinct and reversible:
class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
def change
change_column_default :people, :age, { from: nil, to: 0 }
end
end
Solution 10 - Ruby on-Rails
If you are just setting defaults for certain attributes of a database backed model I'd consider using sql default column values - can you clarify what types of defaults you are using?
There are a number of approaches to handle it, this plugin looks like an interesting option.
Solution 11 - Ruby on-Rails
The suggestion to override new/initialize is probably incomplete. Rails will (frequently) call allocate for ActiveRecord objects, and calls to allocate won't result in calls to initialize.
If you're talking about ActiveRecord objects, take a look at overriding after_initialize.
These blog posts (not mine) are useful:
Default values Default constructors not called
[Edit: SFEley points out that Rails actually does look at the default in the database when it instantiates a new object in memory - I hadn't realized that.]
Solution 12 - Ruby on-Rails
I needed to set a default just as if it was specified as default column value in DB. So it behaves like this
a = Item.new
a.published_at # => my default value
a = Item.new(:published_at => nil)
a.published_at # => nil
Because after_initialize callback is called after setting attributes from arguments, there was no way to know if the attribute is nil because it was never set or because it was intentionally set as nil. So I had to poke inside a bit and came with this simple solution.
class Item < ActiveRecord::Base
def self.column_defaults
super.merge('published_at' => Time.now)
end
end
Works great for me. (Rails 3.2.x)
Solution 13 - Ruby on-Rails
A potentially even better/cleaner potential way than the answers proposed is to overwrite the accessor, like this:
def status
self['name_of_var'] || 'desired_default_value'
end
See "Overwriting default accessors" in the ActiveRecord::Base documentation and more from StackOverflow on using self.
Solution 14 - Ruby on-Rails
i answered a similar question here.. a clean way to do this is using Rails attr_accessor_with_default
class SOF
attr_accessor_with_default :is_awesome,true
end
sof = SOF.new
sof.is_awesome
=> true
UPDATE
attr_accessor_with_default has been deprecated in Rails 3.2.. you could do this instead with pure Ruby
class SOF
attr_writer :is_awesome
def is_awesome
@is_awesome ||= true
end
end
sof = SOF.new
sof.is_awesome
#=> true
Solution 15 - Ruby on-Rails
If you're talking about ActiveRecord objects, I use the 'attribute-defaults' gem.
Documentation & download: https://github.com/bsm/attribute-defaults
Solution 16 - Ruby on-Rails
You could use the rails_default_value gem. eg:
class Foo < ActiveRecord::Base
# ...
default :bar => 'some default value'
# ...
end
Solution 17 - Ruby on-Rails
You can override the constructor for the ActiveRecord model.
Like this:
def initialize(*args)
super(*args)
self.attribute_that_needs_default_value ||= default_value
self.attribute_that_needs_another_default_value ||= another_default_value
#ad nauseum
end