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


当前回答

Rails 5 +

你可以在你的模型中使用属性方法,例如:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end

您还可以将lambda传递给默认参数。例子:

attribute :uuid, :string, default: -> { SecureRandom.uuid }

第二个参数是类型,它也可以是一个自定义类型类实例,例如:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }

其他回答

在rails 3中使用default_scope

接口文档

ActiveRecord模糊了在数据库(模式)中定义的默认值和在应用程序(模型)中完成的默认值之间的区别。在初始化过程中,它解析数据库模式并记录其中指定的任何默认值。稍后,在创建对象时,它将分配那些模式指定的默认值,而不涉及数据库。

讨论

我使用属性默认的宝石

从文档中可以看到: 运行sudo gem install attribute-defaults并添加require 'attribute_defaults'到你的应用中。

class Foo < ActiveRecord::Base
  attr_default :age, 18
  attr_default :last_seen do
    Time.now
  end
end

Foo.new()           # => age: 18, last_seen => "2014-10-17 09:44:27"
Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"

可以通过简单地执行以下操作来改进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)

# 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