Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: How can I set default values in ActiveRecord?

How can I set default value in ActiveRecord?

I see a post from Pratik that describes an ugly, complicated chunk of code: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model

class Item < ActiveRecord::Base     def initialize_with_defaults(attrs = nil, &block)     initialize_without_defaults(attrs) do       setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless         !attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }       setter.call('scheduler_type', 'hotseat')       yield self if block_given?     end   end   alias_method_chain :initialize, :defaults end 

I have seen the following examples googling around:

  def initialize      super     self.status = ACTIVE unless self.status   end 

and

  def after_initialize      return unless new_record?     self.status = ACTIVE   end 

I've also seen people put it in their migration, but I'd rather see it defined in the model code.

Is there a canonical way to set default value for fields in ActiveRecord model?

like image 450
ryw Avatar asked Nov 30 '08 06:11

ryw


People also ask

What is ActiveRecord :: Base in Rails?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending.

Can you use ActiveRecord without rails?

One of the primary aspects of ActiveRecord is that there is very little to no configuration needed. It follow convention over configuration. ActiveRecord is commonly used with the Ruby-on-Rails framework but you can use it with Sinatra or without any web framework if desired.

What is ActiveRecord in Ruby on Rails?

Active Record is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database.

What are models in Rails?

A Rails Model is a Ruby class that can add database records (think of whole rows in an Excel table), find particular data you're looking for, update that data, or remove data. These common operations are referred to by the acronym CRUD--Create, Remove, Update, Destroy.


2 Answers

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:

  1. 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.
  2. 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.
  3. Overriding initialize can work, but don't forget to call super!
  4. 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?
  5. Overriding after_initialize is deprecated as of Rails 3. When I override after_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:

  1. For boolean fields do:

    self.bool_field = true if self.bool_field.nil?

    See Paul Russell's comment on this answer for more details

  2. If you're only selecting a subset of columns for a model (ie; using select in a query like Person.select(:firstname, :lastname).all) you will get a MissingAttributeError if your init method accesses a column that hasn't been included in the select 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)

like image 185
Jeff Perrin Avatar answered Sep 24 '22 13:09

Jeff Perrin


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 } 
like image 27
Lucas Caton Avatar answered Sep 24 '22 13:09

Lucas Caton