views:

1024

answers:

6

I'm trying to find the "best" way to set default values for objects in rails. Best I can think of is to set the default value in the "new" method in the controller. Anyone have any input on if this is acceptable or if there's a better way to do it?

+1  A: 

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
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
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
+2  A: 

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 => 1
  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
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
A: 

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
+3  A: 

"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, :default => "Smith"
  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 GenericWhitePerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Smith"}.merge(attributes)
    super(attr_with_defaults)
  end
end

Then, when you do a GenericWhitePerson.new(), it'll always trickle the "Smith" attribute up to Person.new() unless you override it with something else.

SFEley
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
+1  A: 

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
A: 

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 overriding parameters passed into the initialize method.

Ian Purton