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


当前回答

如果该列恰好是一个“状态”类型的列,并且您的模型适合使用状态机,那么可以考虑使用aasm gem,然后您可以简单地这样做

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

它仍然没有初始化未保存记录的值,但它比使用init或其他方法来滚动自己的记录要简洁一些,而且您还可以获得aasm的其他好处,例如为所有状态设置范围。

其他回答

# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600

我强烈建议使用“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

Phusion有一些很好的插件。

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

使用after_initialize方法。

这个问题已经回答了很长时间了,但是我经常需要默认值,并且不喜欢将它们放在数据库中。我创建了一个DefaultValues关注点:

module DefaultValues
  extend ActiveSupport::Concern

  class_methods do
    def defaults(attr, to: nil, on: :initialize)
      method_name = "set_default_#{attr}"
      send "after_#{on}", method_name.to_sym

      define_method(method_name) do
        if send(attr)
          send(attr)
        else
          value = to.is_a?(Proc) ? to.call : to
          send("#{attr}=", value)
        end
      end

      private method_name
    end
  end
end

然后像这样在我的模型中使用它:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end