我如何在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模型?


当前回答

这就是构造函数的作用!重写模型的initialize方法。

使用after_initialize方法。

其他回答

可以通过简单地执行以下操作来改进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解决方案的问题是,无论是否访问该属性,都必须向从DB中查找的每个对象添加after_initialize。我建议采用惰性加载方法。

属性方法(getter)当然是方法本身,因此您可以覆盖它们并提供默认值。喜欢的东西:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

除非,就像有人指出的那样,你需要执行Foo.find_by_status('ACTIVE')。在这种情况下,我认为您确实需要在数据库约束中设置默认值,如果数据库支持它的话。

这里有一个我用过的解决方案,我有点惊讶还没有加入。

它分为两部分。第一部分是在实际迁移中设置默认值,第二部分是在模型中添加验证,以确保存在为真。

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

您将看到这里已经设置了默认值。现在在验证中,你想要确保字符串总是有一个值,那么就这样做吧

 validates :new_team_signature, presence: true

这将为您设置默认值。(对我来说,我有“Welcome to the Team”),然后它将进一步确保该对象始终存在一个值。

希望有帮助!

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

我强烈建议使用“default_value_for”gem: https://github.com/FooBarWidget/default_value_for

有一些棘手的场景需要重写initialize方法,这个gem就是这样做的。

例子:

你的db默认值是NULL,你的模型/ruby定义的默认值是“一些字符串”,但你实际上想要设置值为nil的原因:MyModel。新(my_attr: nil)

这里的大多数解决方案将无法将值设置为nil,而是将其设置为默认值。

好吧,所以你不用||=方法,而是切换到my_attr_changed?…

但是现在假设你的数据库默认值是"some string",你的模型/ruby定义的默认值是"some other string",但在某种情况下,你想将值设置为"some string"(数据库默认值):MyModel。新(my_attr:“some_string”)

这将导致my_attr_changed?为false是因为该值与db默认值匹配,而db默认值将触发ruby定义的默认代码,并将该值设置为“其他字符串”——同样,这不是您想要的。


由于这些原因,我不认为这可以通过一个after_initialize钩子正确地完成。

同样,我认为“default_value_for”gem采用了正确的方法:https://github.com/FooBarWidget/default_value_for