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


当前回答

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

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

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

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

 validates :new_team_signature, presence: true

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

希望有帮助!

其他回答

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

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

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

来自api文档http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html 在模型中使用before_validation方法,它为创建和更新调用提供了创建特定初始化的选项 例如,在这个例子中(同样摘自API文档的例子),数字字段被初始化为信用卡。您可以轻松地对其进行调整,以设置您想要的任何值

class CreditCard < ActiveRecord::Base
  # Strip everything but digits, so the user can specify "555 234 34" or
  # "5552-3434" or both will mean "55523434"
  before_validation(:on => :create) do
    self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number")
  end
end

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

令人惊讶的是,这里没有提到他

Rails 6.1 +

现在您可以在模型上使用属性方法,而无需设置类型。

attribute :status, default: ACTIVE

or

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

注意,提供默认的to属性不能引用类的实例(lambda将在类的上下文中执行,而不是在实例中执行)。因此,如果需要根据实例或关联动态地将默认值设置为一个值,则仍然必须使用替代方法,例如after_initialize回调。如前所述,如果引用关联,建议将此限制为新记录,以避免n+1个查询。

after_initialize :do_something_that_references_instance_or_associations, if: :new_record?
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
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)