我如何在ActiveRecord设置默认值?

我看到Pratik的一篇文章,描述了一段丑陋而复杂的代码: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

我在谷歌上看到了以下例子:

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

and

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

我也见过有人把它放在迁移中,但我更愿意看到它在模型代码中定义。

是否有一个规范的方法来设置默认值的字段在ActiveRecord模型?


当前回答

可以通过简单地执行以下操作来改进after_initialize回调模式

after_initialize :some_method_goes_here, :if => :new_record?

如果您的init代码需要处理关联,那么这就有一个非常重要的好处,因为如果您读取初始记录而不包括关联记录,那么下面的代码将触发一个微妙的n+1。

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end

其他回答

每个可用的方法都有几个问题,但我认为定义一个after_initialize回调是正确的方法,原因如下:

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. 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. Overriding initialize can work, but don't forget to call super! 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? 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:

弃用警告:Base#after_initialize已弃用,请使用Base。After_initialize:方法代替。(调用from /Users/me/myapp/app/models/my_model:15)

因此,我要说的是写一个after_initialize回调函数,它允许你在关联上设置默认属性,就像这样:

  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    

现在您只有一个地方可以寻找模型的初始化。我一直在用这个方法,直到有人提出更好的方法。

警告:

For boolean fields do: self.bool_field = true if self.bool_field.nil? See Paul Russell's comment on this answer for more details 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)

我们通过迁移(通过在每个列定义上指定:default选项)将默认值放入数据库中,并让Active Record使用这些值为每个属性设置默认值。

恕我直言,这种方法与AR的原则是一致的:约定高于配置,DRY,表定义驱动模型,而不是相反。

注意,默认值仍然在应用程序(Ruby)代码中,尽管不在模型中,但在迁移中。

After_initialize方法已弃用,请改用回调方法。

after_initialize :defaults

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

然而,在迁移中使用:default仍然是最干净的方式。

我发现使用验证方法对设置默认值提供了很多控制。您甚至可以为更新设置默认值(或失败验证)。如果需要的话,您甚至可以为插入和更新设置不同的默认值。 注意,默认值不会设置,直到#valid?被称为。

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

关于定义after_initialize方法,可能会有性能问题,因为after_initialize也会被:find返回的每个对象调用: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find

class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end