Ruby-on-rails 如何在 ActiveRecord 中设置默认值?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/328525/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I set default values in ActiveRecord?
提问by ryw
How can I set default value in ActiveRecord?
如何在 ActiveRecord 中设置默认值?
I see a post from Pratik that describes an ugly, complicated chunk of code: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model
我看到 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
I have seen the following examples googling around:
我在谷歌上看到了以下示例:
def initialize
super
self.status = ACTIVE unless self.status
end
and
和
def after_initialize
return unless new_record?
self.status = ACTIVE
end
I've also seen people put it in their migration, but I'd rather see it defined in the model code.
我也看到人们把它放在他们的迁移中,但我宁愿看到它在模型代码中定义。
Is there a canonical way to set default value for fields in ActiveRecord model?
有没有一种规范的方法来为 ActiveRecord 模型中的字段设置默认值?
回答by Jeff Perrin
There are several issues with each of the available methods, but I believe that defining an after_initializecallback is the way to go for the following reasons:
每种可用方法都有几个问题,但我相信定义after_initialize回调是可行的方法,原因如下:
default_scopewill 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 notwhat you want.- Defining defaults in your migration also works part of the time... As has already been mentioned this will notwork when you just call Model.new.
- Overriding
initializecan work, but don't forget to callsuper! - 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_initializeis deprecatedas of Rails 3. When I overrideafter_initializein rails 3.0.3 I get the following warning in the console:
default_scope将为新模型初始化值,但这将成为您找到模型的范围。如果您只想将一些数字初始化为 0,那么这不是您想要的。- 在迁移定义的默认值也工作在部分时间......正如已经提到的,这将不工作的时候,你只需要调用Model.new。
- 覆盖
initialize可以工作,但不要忘记调用super! - 使用像 phusion 这样的插件有点荒谬。这是 ruby,我们真的需要一个插件来初始化一些默认值吗?
- 从 Rails 3 开始
after_initialize不推荐使用覆盖。当我after_initialize在 Rails 3.0.3 中覆盖时,我在控制台中收到以下警告:
DEPRECATION WARNING: Base#after_initialize has been deprecated, please use Base.after_initialize :method instead. (called from /Users/me/myapp/app/models/my_model:15)
弃用警告: Base#after_initialize 已弃用,请改用 Base.after_initialize :method。(从 /Users/me/myapp/app/models/my_model:15 调用)
Therefore I'd say write an after_initializecallback, which lets you default attributes in addition toletting you set defaults on associations like so:
因此,我会说编写一个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
Now you have just oneplace to look for initialization of your models. I'm using this method until someone comes up with a better one.
现在您只有一个地方可以查找模型的初始化。我一直在使用这种方法,直到有人想出更好的方法。
Caveats:
注意事项:
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
selectin a query likePerson.select(:firstname, :lastname).all) you will get aMissingAttributeErrorif yourinitmethod accesses a column that hasn't been included in theselectclause. You can guard against this case like so:self.number ||= 0.0 if self.has_attribute? :numberand 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)
对于布尔字段,请执行以下操作:
self.bool_field = true if self.bool_field.nil?有关更多详细信息,请参阅 Paul Russell 对此答案的评论
如果您只是为模型选择列的子集(即;
select在像 一样的查询中使用Person.select(:firstname, :lastname).all),MissingAttributeError如果您的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 评论)
回答by Lucas Caton
Rails 5+
导轨 5+
You can use the attributemethod within your models, eg.:
您可以在模型中使用属性方法,例如:
class Account < ApplicationRecord
attribute :locale, :string, default: 'en'
end
You can also pass a lambda to the defaultparameter. Example:
您还可以将 lambda 传递给default参数。例子:
attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
回答by Laurent Farcy
We put the default values in the database through migrations (by specifying the :defaultoption on each column definition) and let Active Record use these values to set the default for each attribute.
我们通过迁移(通过:default在每个列定义上指定选项)将默认值放入数据库中,并让 Active Record 使用这些值为每个属性设置默认值。
IMHO, this approach is aligned with the principles of AR : convention over configuration, DRY, the table definition drives the model, not the other way around.
恕我直言,这种方法符合 AR 的原则:约定优于配置,DRY,表定义驱动模型,而不是相反。
Note that the defaults are still in the application (Ruby) code, though not in the model but in the migration(s).
请注意,默认值仍在应用程序 (Ruby) 代码中,尽管不在模型中,而是在迁移中。
回答by Joseph Lord
Some simple cases can be handled by defining a default in the database schema but that doesn't handle a number of trickier cases including calculated values and keys of other models. For these cases I do this:
一些简单的情况可以通过在数据库模式中定义默认值来处理,但这不能处理许多棘手的情况,包括其他模型的计算值和键。对于这些情况,我这样做:
after_initialize :defaults
def defaults
unless persisted?
self.extras||={}
self.other_stuff||="This stuff"
self.assoc = [OtherModel.find_by_name('special')]
end
end
I've decided to use the after_initialize but I don't want it to be applied to objects that are found only those new or created. I think it is almost shocking that an after_new callback isn't provided for this obvious use case but I've made do by confirming whether the object is already persisted indicating that it isn't new.
我决定使用 after_initialize 但我不希望它应用于仅发现那些新的或创建的对象。我认为几乎令人震惊的是,没有为这个明显的用例提供 after_new 回调,但我已经通过确认对象是否已经持久化表明它不是新的来做到这一点。
Having seen Brad Murray's answer this is even cleaner if the condition is moved to callback request:
看过 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
回答by Brad Murray
The after_initialize callback pattern can be improved by simply doing the following
可以通过简单地执行以下操作来改进 after_initialize 回调模式
after_initialize :some_method_goes_here, :if => :new_record?
This has a non-trivial benefit if your init code needs to deal with associations, as the following code triggers a subtle n+1 if you read the initial record without including the associated.
如果您的 init 代码需要处理关联,这有一个非常重要的好处,因为如果您在不包含关联的情况下读取初始记录,以下代码会触发一个微妙的 n+1。
class Account
has_one :config
after_initialize :init_config
def init_config
self.config ||= build_config
end
end
回答by aidan
I use the attribute-defaultsgem
From the documentation:
run sudo gem install attribute-defaultsand add require 'attribute_defaults'to your app.
从文档:运行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"
回答by peterhurford
An even better/cleaner potential way than the answers proposed is to overwrite the accessor, like this:
比建议的答案更好/更清洁的潜在方法是覆盖访问器,如下所示:
def status
self['status'] || ACTIVE
end
See "Overwriting default accessors" in the ActiveRecord::Base documentationand more from StackOverflow on using self.
请参阅ActiveRecord::Base 文档中的“覆盖默认访问器”以及StackOverflow 中有关使用 self 的更多信息。
回答by Blair Anderson
Similar questions, but all have slightly different context: - How do I create a default value for attributes in Rails activerecord's model?
类似的问题,但都略有不同的上下文: -如何为 Rails activerecord 模型中的属性创建默认值?
Best Answer: Depends on What You Want!
最佳答案:看你想要什么!
If you want every object tostart with a value:use after_initialize :init
如果您希望每个对象都以一个值开头:使用after_initialize :init
You want the new.htmlform to have a default value upon opening the page? use https://stackoverflow.com/a/5127684/1536309
您希望new.html表单在打开页面时具有默认值吗?使用https://stackoverflow.com/a/5127684/1536309
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
If you want every object tohave a value calculated from user input:use before_save :default_valuesYou want user to enter Xand then Y = X+'foo'? use:
如果您希望每个对象都有一个根据用户输入计算的值:使用before_save :default_values您希望用户输入X然后Y = X+'foo'? 用:
class Task < ActiveRecord::Base
before_save :default_values
def default_values
self.status ||= 'P'
end
end
回答by John Topley
This is what constructors are for! Override the model's initializemethod.
这就是构造函数的用途!覆盖模型的initialize方法。
Use the after_initializemethod.
使用after_initialize方法。

