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


当前回答

来自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

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

其他回答

这个问题已经回答了很长时间了,但是我经常需要默认值,并且不喜欢将它们放在数据库中。我创建了一个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

尽管在大多数情况下这样设置默认值会令人困惑和尴尬,但您也可以使用:default_scope。点击这里查看squil的评论。

我也看到人们在迁徙中使用这种方法,但我更愿意看到 在模型代码中定义。 是否有一个规范的方法来设置字段的默认值 ActiveRecord模型?

在Rails 5之前,规范的Rails方法实际上是在迁移中设置它,只需要在db/schema中查找即可。rb每当想要看到什么默认值是由DB为任何模型设置。

与@Jeff Perrin的回答相反(这有点老),在使用Model时,迁移方法甚至会应用默认值。新的,由于一些Rails魔法。在Rails 4.1.16中验证。

最简单的东西往往是最好的。较少的知识负债和代码库中的潜在混淆点。而且它‘just works’。

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

或者,在不创建新列的情况下更改列,然后执行以下操作:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

或者更好:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

查看官方RoR指南以获得列更改方法中的选项。

null: false禁止在DB中使用null值,而且,作为一个额外的好处,它还会进行更新,以便所有先前为null的DB记录也会使用该字段的默认值设置。如果您愿意,您可以在迁移中排除这个参数,但我发现它非常方便!

Rails 5+的规范方式是,正如@Lucas Caton所说:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end

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

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

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

我已经用了一段时间了。

# post.rb
class Post < ApplicationRecord
  attribute :country, :string, default: 'ID'
end