如何在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
和
def after_initialize return unless new_record? self.status = ACTIVE end
我也看到人们把它放在他们的迁移中,但我宁愿看到它在模型代码中定义.
是否有规范方法为ActiveRecord模型中的字段设置默认值?
每种可用方法都存在几个问题,但我认为定义after_initialize
回调是出于以下原因的方法:
default_scope
将初始化新模型的值,但随后将成为您找到模型的范围.如果您只想将某些数字初始化为0,那么这不是您想要的.
在迁移定义的默认值也工作在部分时间......正如已经提到这个会不会当你只需要调用Model.new工作.
覆盖initialize
可以工作,但不要忘记打电话super
!
使用像phusion这样的插件有点荒谬.这是红宝石,我们真的需要一个插件来初始化一些默认值吗?
从Rails 3开始,after_initialize
不推荐使用覆盖.当我after_initialize
在rails 3.0.3中覆盖时,我在控制台中收到以下警告:
弃用警告:不推荐使用Base#after_initialize,请改用Base.after_initialize:方法.(来自/ 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
现在您只有一个地方可以查找模型的初始化.我正在使用这种方法,直到有人想出一个更好的方法.
注意事项:
对于布尔字段,执行:
self.bool_field = true if self.bool_field.nil?
有关详细信息,请参阅Paul Russell对此答案的评论
如果您只为模型选择列的子集(即; select
在类似查询中使用Person.select(:firstname, :lastname).all
),您将获得一个MissingAttributeError
if您的init
方法是否访问未包含在该select
子句中的列.你可以这样防范这种情况:
self.number ||= 0.0 if self.has_attribute? :number
并为布尔列...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
另请注意,Rails 3.2之前的语法不同(请参阅下面的Cliff Darling的评论)
在Rails 5+中,您可以在模型中使用属性方法,例如:
class Account < ApplicationRecord
attribute :locale, :string, default: 'en'
end
我们通过迁移(通过:default
在每个列定义上指定选项)将默认值放在数据库中,并让Active Record使用这些值来设置每个属性的默认值.
恕我直言,这种方法符合AR的原则:约定优于配置,DRY,表定义驱动模型,而不是相反.
请注意,默认值仍在应用程序(Ruby)代码中,但不在模型中,而是在迁移中.
一些简单的情况可以通过在数据库模式中定义默认值来处理,但是不能处理许多棘手的情况,包括计算值和其他模型的键.对于这些情况,我这样做:
after_initialize :defaults def defaults unless persisted? self.extras||={} self.other_stuff||="This stuff" self.assoc = [OtherModel.find_by_name('special')] end end
我决定使用after_initialize,但我不希望它应用于只找到新的或创建的对象.我认为对于这个明显的用例没有提供after_new回调几乎令人震惊,但我已经通过确认该对象是否已经持久化表明它不是新的来做到了.
看过Brad Murray的回答,如果条件转移到回调请求,这甚至更清晰:
after_initialize :defaults, unless: :persisted? # ":if => :new_record?" is equivalent in this context def defaults self.extras||={} self.other_stuff||="This stuff" self.assoc = [OtherModel.find_by_name('special')] end
只需执行以下操作即可改进after_initialize回调模式
after_initialize :some_method_goes_here, :if => :new_record?
如果您的初始化代码需要处理关联,这会带来非常重要的好处,因为如果您在不包含关联的情况下读取初始记录,则以下代码会触发一个微妙的n + 1.
class Account has_one :config after_initialize :init_config def init_config self.config ||= build_config end end
Phusion的家伙有一些不错的插件.
我用的是attribute-defaults
宝石
从文档:运行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"
比建议的答案更好/更清晰的潜在方式是覆盖访问器,如下所示:
def status self['status'] || ACTIVE end
请参阅ActiveRecord :: Base文档中的 "覆盖默认访问器" 以及使用self时StackOverflow中的更多信息.
类似的问题,但都有不同的上下文: - 如何在Rails activerecord的模型中为属性创建默认值?
最佳答案:取决于你想要的!
如果您希望每个对象都以值开头:使用after_initialize :init
您希望new.html
表单在打开页面时具有默认值吗?使用/sf/ask/17360801/
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
如果您希望每个对象都具有根据用户输入计算的值:使用before_save :default_values
您希望用户输入X
然后Y = X+'foo'
?使用:
class Task < ActiveRecord::Base before_save :default_values def default_values self.status ||= 'P' end end