How can I set default values in ActiveRecord?

2019-01-02 19:10发布

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?

25条回答
栀子花@的思念
2楼-- · 2019-01-02 19:33

after_initialize method is deprecated, use the callback instead.

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

however, using :default in your migrations is still the cleanest way.

查看更多
余生请多指教
3楼-- · 2019-01-02 19:34

I've found that using a validation method provides a lot of control over setting defaults. You can even set defaults (or fail validation) for updates. You even set a different default value for inserts vs updates if you really wanted to. Note that the default won't be set until #valid? is called.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

Regarding defining an after_initialize method, there could be performance issues because after_initialize is also called by each object returned by :find : http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find

查看更多
不流泪的眼
4楼-- · 2019-01-02 19:35
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
查看更多
几人难应
5楼-- · 2019-01-02 19:36

This is what constructors are for! Override the model's initialize method.

Use the after_initialize method.

查看更多
听够珍惜
6楼-- · 2019-01-02 19:36

I ran into problems with after_initialize giving ActiveModel::MissingAttributeError errors when doing complex finds:

eg:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

"search" in the .where is hash of conditions

So I ended up doing it by overriding initialize in this way:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

The super call is necessary to make sure the object initializing correctly from ActiveRecord::Base before doing my customize code, ie: default_values

查看更多
谁念西风独自凉
7楼-- · 2019-01-02 19:46

Although doing that for setting default values is confusing and awkward in most cases, you can use :default_scope as well. Check out squil's comment here.

查看更多
登录 后发表回答