117

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?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
biagidp
  • 2,175
  • 3
  • 18
  • 29
  • 1
    What are these objects; how are they consumed / used ? Are they used while rendering the views or for controller logic ? – Gishu Jul 27 '09 at 04:19
  • 3
    If you are talking about an ActiveRecord object I have to tell you that there's no sane solution to the 'default values' problem. Only insane hacks, and the rails authors don't seem to think that the feature is worth it (amazing as only the rails community is..) – Mauricio Nov 19 '10 at 18:39
  • Since the accepted and most answers focus on ActiveRecords, we assume the the original question was about AcitveRecords. Therefore possible duplicate of http://stackoverflow.com/questions/328525/how-can-i-set-default-values-in-activerecord – Ciro Santilli OurBigBook.com Jan 14 '14 at 20:51
  • Possible duplicate of [How can I set default values in ActiveRecord?](https://stackoverflow.com/questions/328525/how-can-i-set-default-values-in-activerecord) – Lucas Caton Jul 14 '17 at 00:49

17 Answers17

107

"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.

Stéphane Bruckert
  • 21,706
  • 14
  • 92
  • 130
SFEley
  • 7,660
  • 5
  • 28
  • 31
  • Second option is actually not gonna work: http://www.3hv.co.uk/blog/2009/06/03/constructors-in-ruby-are-not-guaranteed-to-be-called/ But thanks for the migration snippet! – Nikita Rybak Jun 18 '10 at 22:50
  • 2
    Try it. It'll work on new model objects called with the `.new` class method. That blog post's discussion about ActiveRecord directly calling `.allocate` was about model objects loaded with existing data from the database. (_And_ it's a terrible idea for ActiveRecord to work that way, IMO. But that's beside the point.) – SFEley Mar 10 '11 at 07:55
  • 1
    @SFEley OP didn't say anything about `new` method, so it's perfectly reasonable to mention this quirk here. – Nikita Rybak Mar 10 '11 at 08:46
  • He did mention _'the "new" method in the controller.'_ What's the likelihood that you're going to load existing records from `controller#new` -- and if you do, why would you be worried about setting default values on those records? – SFEley Mar 18 '11 at 16:34
  • @SFEley Are you implying that people using Rails do not need defaults? And what `controller#new` has got to do with it? – Nikita Rybak Mar 18 '11 at 23:55
  • 2
    If you step back to read the original poster's question again, Nikita, and then my comments in order, it may make more sense to you. If not... Well, the question's been answered. Have a nice day. – SFEley Mar 22 '11 at 03:06
  • Workaround for the down migration is to execute code directly on the database. The code is less portable, but that's not usually a problem in practice. MySQL example: `def self.down execute 'alter table people alter column last_name drop default' end` It's better than blocking your rollbacks with exceptions IMO. – dekeguard May 12 '12 at 04:35
  • 8
    Better for the down migration: `change_column_default :people, :last_name, nil` http://stackoverflow.com/a/1746246/483520 – Nolan Amy Jul 19 '12 at 19:29
  • 9
    Please note that for newer rails versions, you need an additional type parameter before the default parameter. Search for :type on this page. – Ben Wheeler Mar 20 '14 at 15:47
  • 1
    Looks like this no longer works? `undefined method 'to_sym' for {:default=>"foo"}` – Joel Brewer Jan 20 '15 at 18:50
  • 3
    @JoelBrewer I ran into this today - for Rails 4, you also need to specify the column type: `change_column :people, :last_name, :string, default: "Doe"` – GoBusto Apr 24 '15 at 10:37
  • If `def initialize(attributes=nil)` doesn't work, due to "no implicit conversion of nil into Hash", try `def initialize(attributes={})`. – neverlastn Jul 09 '17 at 07:42
60

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
Ultrasaurus
  • 3,031
  • 2
  • 33
  • 52
J-_-L
  • 9,079
  • 2
  • 40
  • 37
  • 2
    Beware this does NOT work on Rails 4.2.x (not tested on later versions). as `change_column` can be quite "structuring", the reverse operation cannot be deduced. If you're not sure, just test it by running `db:migrate` and `db:rollback` right after. Accepted answer as the same result but at least it's assumed ! – gfd Feb 21 '17 at 13:19
  • 1
    This is the correct answer for me.. It works with rails 5.1 but you will need to do `up` and `down` like described above from @GoBusto – Fabrizio Bertoglio May 11 '17 at 12:29
23

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Ian Purton
  • 15,331
  • 2
  • 27
  • 26
16

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

change_column_default Rails API docs

Sebastiaan Pouyet
  • 1,346
  • 9
  • 10
  • 1
    Accepted answer is more complete but I like this clean and documented one... Thanks ! – gfd Feb 21 '17 at 13:23
8

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
Paulo Fidalgo
  • 21,709
  • 7
  • 99
  • 115
7

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

Vlad Zloteanu
  • 8,464
  • 3
  • 41
  • 58
  • 2
    Isn't the problem with using defaults in the database that they don't get set til the object is saved? If I want to construct a new object with the defaults populated, I need to set them in the initializer. – Rafe Jul 27 '09 at 06:00
  • Booleans can't default to 1 or 0 -- they must be set to true or false (see Silasj's answer). – Jamon Holmgren Jul 06 '12 at 16:32
  • @JamonHolmgren Thanks for the remark, I corrected the answer :) – Vlad Zloteanu Dec 03 '12 at 19:58
7

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.

erroric
  • 991
  • 1
  • 11
  • 22
  • This doesn't work as I expected - it was supposed to only set the default on new, but it also sets the default to the attribute for each object that is found and instantiated (i.e. records loaded from the db). You can do a check an object attribute looking for values before assigning the default, but that is not a very elegant or robust solution. – erroric Oct 11 '12 at 13:35
  • 1
    Why you recommend `after_initialize` callback here ? Rails documentation on callbacks has example of setting default value in `before_create` without extra condition checks – Dfr Jan 15 '13 at 16:23
  • @Dfr - I must have missed that, can you throw me a link to review and I'll update the answer... – erroric Jan 17 '13 at 14:00
  • Yes, here http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html first example, class `Subscription` – Dfr Jan 17 '13 at 14:56
  • 5
    @Dfr - The reason that we are using the `after_initialize`, instead of the `before_create` callback, is we want to set a default value for the _user_ (for use in a view) when they are creating new objects. The `before_create` callback is called **after** the _user_ has been served a new object, provided their input, and submitted the object for creation to the controller. The controller then checks for any `before_create` callbacks. It's seems counter-intuitive, but it a nomenclature thing - `before_create` refers to the `create` action. Instantiating a new object does not `create` the object. – erroric Jan 22 '13 at 15:55
  • The new record idea worked pretty well for me, though it looks like this: `after_initialize :assign_defaults_on_new_Foo, :if => Proc.new{|f| f.new_record? }` – ideaoforder Jan 23 '13 at 19:26
5

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.

Silasj
  • 51
  • 1
  • 1
4

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
Pere Joan Martorell
  • 2,608
  • 30
  • 29
1

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.

paulthenerd
  • 9,487
  • 2
  • 35
  • 29
  • 1
    I think you shouldn't depend on database to deal with defaults and constraints, all that stuff should be sorted out in model layer. That plugin works only for ActiveRecord models, it's not generic way to set defaults for objects. – Lukas Stejskal Jul 27 '09 at 09:51
  • I'd argue that it depends on the types of defaults you are trying to use, it'd not something I've needed to do very often but I'd say constraints are in fact much better off being set in both the database and the model - to prevent your data from becoming invalid in the database. – paulthenerd Jul 27 '09 at 13:47
1

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.]

James Moore
  • 8,636
  • 5
  • 71
  • 90
1

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)

1

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.

Community
  • 1
  • 1
peterhurford
  • 1,074
  • 1
  • 13
  • 21
0

If you're talking about ActiveRecord objects, I use the 'attribute-defaults' gem.

Documentation & download: https://github.com/bsm/attribute-defaults

aidan
  • 9,310
  • 8
  • 68
  • 82
0

You could use the rails_default_value gem. eg:

class Foo < ActiveRecord::Base
  # ...
  default :bar => 'some default value'
  # ...
end

https://github.com/keithrowell/rails_default_value

0

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
Community
  • 1
  • 1
Orlando
  • 9,374
  • 3
  • 56
  • 53
-3

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
Terry
  • 1,088
  • 6
  • 10
  • 2
    This is a terrible place to go about changing core rails functionality. There are other much stable less likely to break other things, ways to do this mentioned in other answers. Monkey patching should only be a last resort for things that cannot possibly be done any other way. – Will Jul 12 '12 at 03:57