Rails:如何在 ActiveRecord 中设置默认值?

发布于 2024-07-10 06:12:23 字数 1034 浏览 10 评论 0原文

如何在 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 模型中字段的默认值?

How can I set default value in 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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(29

肥爪爪 2024-07-17 06:12:24

比建议的答案更好/更干净的潜在方法是覆盖访问器,如下所示:

def status
  self['status'] || ACTIVE
end

请参阅 ActiveRecord::Base 文档来自 StackOverflow 的有关使用 self 的更多信息

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 documentation and more from StackOverflow on using self.

抽个烟儿 2024-07-17 06:12:24

我使用 attribute-defaults gem

从文档中:
运行 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"

I use the attribute-defaults gem

From the documentation:
run sudo gem install attribute-defaults and add require 'attribute_defaults' to your app.

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"
棒棒糖 2024-07-17 06:12:24

类似的问题,但背景都略有不同:
- 如何创建Rails activerecord 模型中属性的默认值?

最佳答案:取决于您想要什么!

如果您希望每个对象< em> 以一个值开始: 使用 after_initialize :init

您希望 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 

如果您希望每个对象有一个根据用户输入计算得出的值:使用before_save :default_values
您希望用户输入X,然后输入Y = X+'foo'? 使用:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end

Similar questions, but all have slightly different context:
- How do I create a default value for attributes in Rails activerecord's model?

Best Answer: Depends on What You Want!

If you want every object to start with a value: use after_initialize :init

You want the new.html form to have a default value upon opening the page? use 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 to have a value calculated from user input: use before_save :default_values
You want user to enter X and then Y = X+'foo'? use:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end
揽月 2024-07-17 06:12:24

我也看到人们将其放入迁移中,但我更愿意看到它
在模型代码中定义。

是否有规范的方法来设置字段的默认值
ActiveRecord 模型?

在 Rails 5 之前,规范的 Rails 方法实际上是在迁移中设置它,只要在 db/schema.rb 中查看数据库为数据库设置的默认值即可。任何型号。

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

最简单的事情往往是最好的。 减少代码库中的知识债务和潜在的混乱点。 而且它“确实有效”。

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 不允许数据库中存在 NULL 值,而且作为一个额外的好处,它还会更新,以便以前为 null 的所有预先存在的数据库记录都设置为此字段的默认值:出色地。 如果您愿意,您可以在迁移中排除此参数,但我发现它非常方便!

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

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
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?

The canonical Rails way, before Rails 5, was actually to set it in the migration, and just look in the db/schema.rb for whenever wanting to see what default values are being set by the DB for any model.

Contrary to what @Jeff Perrin answer states (which is a bit old), the migration approach will even apply the default when using Model.new, due to some Rails magic. Verified working in Rails 4.1.16.

The simplest thing is often the best. Less knowledge debt and potential points of confusion in the codebase. And it 'just works'.

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

Or, for column change without creating a new one, then do either:

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

Or perhaps even better:

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

Check the official RoR guide for options in column change methods.

The null: false disallows NULL values in the DB, and, as an added benefit, it also updates so that all pre-existing DB records that were previously null is set with the default value for this field as well. You may exclude this parameter in the migration if you wish, but I found it very handy!

The canonical way in Rails 5+ is, as @Lucas Caton said:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end
独木成林 2024-07-17 06:12:24

这就是构造函数的用途! 重写模型的 initialize 方法。

使用 after_initialize 方法。

This is what constructors are for! Override the model's initialize method.

Use the after_initialize method.

成熟的代价 2024-07-17 06:12:24

各位,我最终做了以下事情:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

很有魅力!

Sup guys, I ended up doing the following:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

Works like a charm!

公布 2024-07-17 06:12:24

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

This has been answered for a long time, but I need default values frequently and prefer not to put them in the database. I create a DefaultValues concern:

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

And then use it in my models like so:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end
柒七 2024-07-17 06:12:24

在进行复杂查找时,我遇到了 after_initialize 给出 ActiveModel::MissingAttributeError 错误的问题:

例如:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

.where 中的“search”是哈希值因此,

我最终通过以下方式重写初始化来完成:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

在执行自定义代码之前,必须调用 super 来确保从 ActiveRecord::Base 正确初始化对象,即:default_values

I ran into problems with after_initialize giving ActiveModel::MissingAttributeError errors when doing complex finds:

eg:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

"search" in the .where is hash of conditions

So I ended up doing it by overriding initialize in this way:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

The super call is necessary to make sure the object initializing correctly from ActiveRecord::Base before doing my customize code, ie: default_values

情泪▽动烟 2024-07-17 06:12:24

after_initialize 方法已弃用,请改用回调。

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

但是,在迁移中使用 :default 仍然是最简洁的方法。

after_initialize method is deprecated, use the callback instead.

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

however, using :default in your migrations is still the cleanest way.

热鲨 2024-07-17 06:12:24

after_initialize 解决方案的问题在于,您必须将 after_initialize 添加到从数据库中查找的每个对象,无论您是否访问此属性。 我建议采用延迟加载的方法。

当然,属性方法(getter)本身就是方法,因此您可以覆盖它们并提供默认值。 像这样的事情:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

除非像有人指出的那样,你需要执行 Foo.find_by_status('ACTIVE') 。 在这种情况下,我认为如果数据库支持的话,您确实需要在数据库约束中设置默认值。

The problem with the after_initialize solutions is that you have to add an after_initialize to every single object you look up out of the DB, regardless of whether you access this attribute or not. I suggest a lazy-loaded approach.

The attribute methods (getters) are of course methods themselves, so you can override them and provide a default. Something like:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

Unless, like someone pointed out, you need to do Foo.find_by_status('ACTIVE'). In that case I think you'd really need to set the default in your database constraints, if the DB supports it.

清醇 2024-07-17 06:12:24
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
温柔少女心 2024-07-17 06:12:24

我强烈建议使用“default_value_for” gem: https://github.com/FooBarWidget/default_value_for

有一些棘手的场景几乎需要覆盖 gem 所做的初始化方法。

示例:

您的数据库默认值是 NULL,您的模型/ruby 定义的默认值是“某个字符串”,但您实际上想要出于某种原因将该值设置为 nil:MyModel.new(my_attr : nil)

这里的大多数解决方案都无法将该值设置为 nil,而是将其设置为默认值。

好的,所以不要采用 ||= 方法,而是切换到 my_attr_changed?...

BUT 现在想象一下您的数据库默认值是“some string”,您的模型/ruby 定义的默认值是“某个其他字符串”,但在某种情况下,您希望将该值设置为“某个字符串”(数据库默认值):MyModel.new(my_attr: 'some_string')

这将导致 my_attr_changed?false 因为该值与数据库默认值匹配,这反过来会触发您的ruby 定义的默认代码并将值设置为“其他字符串”——同样,这不是您想要的。


出于这些原因,我认为仅使用 after_initialize 挂钩就无法正确完成此操作。

再次,我认为“default_value_for”gem 正在采取正确的方法: https://github.com/FooBarWidget/default_value_for< /a>

I strongly suggest using the "default_value_for" gem: https://github.com/FooBarWidget/default_value_for

There are some tricky scenarios that pretty much require overriding the initialize method, which that gem does.

Examples:

Your db default is NULL, your model/ruby-defined default is "some string", but you actually want to set the value to nil for whatever reason: MyModel.new(my_attr: nil)

Most solutions here will fail to set the value to nil, and will instead set it to the default.

OK, so instead of taking the ||= approach, you switch to my_attr_changed?...

BUT now imagine your db default is "some string", your model/ruby-defined default is "some other string", but under a certain scenario, you want to set the value to "some string" (the db default): MyModel.new(my_attr: 'some_string')

This will result in my_attr_changed? being false because the value matches the db default, which in turn will fire your ruby-defined default code and set the value to "some other string" -- again, not what you desired.


For those reasons I don't think this can properly be accomplished with just an after_initialize hook.

Again, I think the "default_value_for" gem is taking the right approach: https://github.com/FooBarWidget/default_value_for

薄凉少年不暖心 2024-07-17 06:12:24

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

Although doing that for setting default values is confusing and awkward in most cases, you can use :default_scope as well. Check out squil's comment here.

—━☆沉默づ 2024-07-17 06:12:24

我发现使用验证方法可以对设置默认值提供很多控制。 您甚至可以设置更新的默认值(或验证失败)。 如果您确实愿意,您甚至可以为插入和更新设置不同的默认值。
请注意,直到 #valid? 才会设置默认值? 叫做。

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

关于定义 after_initialize 方法,可能会出现性能问题,因为 after_initialize 也会被 :find 返回的每个对象调用:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find

I've found that using a validation method provides a lot of control over setting defaults. You can even set defaults (or fail validation) for updates. You even set a different default value for inserts vs updates if you really wanted to.
Note that the default won't be set until #valid? is called.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

Regarding defining an after_initialize method, there could be performance issues because after_initialize is also called by each object returned by :find :
http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find

独守阴晴ぅ圆缺 2024-07-17 06:12:24

如果该列恰好是“状态”类型列,并且您的模型适合使用状态机,请考虑使用 aasm gem,之后你可以简单地执行

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

它仍然不会初始化未保存记录的值,但它比使用 init 或其他东西滚动你自己的值要干净一些,你会收获aasm 的其他好处,例如适用于您所有状态的范围。

If the column happens to be a 'status' type column, and your model lends itself to the use of state machines, consider using the aasm gem, after which you can simply do

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

It still doesn't initialize the value for unsaved records, but it's a bit cleaner than rolling your own with init or whatever, and you reap the other benefits of aasm such as scopes for all your statuses.

请恋爱 2024-07-17 06:12:24

https://github.com/keithrowell/rails_default_value

class Task < ActiveRecord::Base
  default :status => 'active'
end

https://github.com/keithrowell/rails_default_value

class Task < ActiveRecord::Base
  default :status => 'active'
end
零時差 2024-07-17 06:12:24

这是我使用过的解决方案,令我有点惊讶的是尚未添加。

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

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

所以你会在这里看到默认值已经设置。 现在,在验证中,您希望确保字符串始终有一个值,因此只需执行

 validates :new_team_signature, presence: true

此操作即可为您设置默认值。 (对我来说,我有“欢迎加入团队”),然后它将更进一步,确保该对象始终存在价值。

希望有帮助!

Here's a solution I've used that I was a little surprised hasn't been added yet.

There are two parts to it. First part is setting the default in the actual migration, and the second part is adding a validation in the model ensuring that the presence is true.

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

So you'll see here that the default is already set. Now in the validation you want to ensure that there is always a value for the string, so just do

 validates :new_team_signature, presence: true

What this will do is set the default value for you. (for me I have "Welcome to the Team"), and then it will go one step further an ensure that there always is a value present for that object.

Hope that helps!

看海 2024-07-17 06:12:24
# 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
# 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
黑寡妇 2024-07-17 06:12:24

我在开发 Rails 6 应用程序时遇到了类似的挑战。

这是我解决问题的方法

我有一个 Users 表和一个 Roles 表。 Users 表属于Roles 表。 我还有一个从 Users 表继承的 AdminStudent 模型。

然后,它要求我在创建用户时为角色设置默认值,例如具有 id = 1studentadmin 角色id = 2 的角色。

class User::Admin < User
  before_save :default_values

  def default_values
    # set role_id to '1' except if role_id is not empty
    return self.role_id = '1' unless role_id.nil?
  end
end

这意味着,在数据库中创建/保存 admin 用户之前,如果 role_id 不为空,则将其设置为默认值 1

return self.role_id = '1' unless role_id.nil? 

与: 相同,

return self.role_id = '1' unless self.role_id.nil?

与: 相同,

self.role_id = '1' if role_id.nil?

但第一个更干净、更精确。

就这样。

我希望这有帮助

I had a similar challenge when working on a Rails 6 application.

Here's how I solved it:

I have a Users table and a Roles table. The Users table belongs to the Roles table. I also have an Admin and Student Models that inherit from the Users table.

It then required that I set a default value for the role whenever a user is created, say admin role that has an id = 1 or student role that has an id = 2.

class User::Admin < User
  before_save :default_values

  def default_values
    # set role_id to '1' except if role_id is not empty
    return self.role_id = '1' unless role_id.nil?
  end
end

This means that before an admin user is created/saved in the database the role_id is set to a default of 1 if it is not empty.

return self.role_id = '1' unless role_id.nil? 

is the same as:

return self.role_id = '1' unless self.role_id.nil?

and the same as:

self.role_id = '1' if role_id.nil?

but the first one is cleaner and more precise.

That's all.

I hope this helps

小鸟爱天空丶 2024-07-17 06:12:24

已经使用这个有一段时间了。

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

Been using this for a while.

# post.rb
class Post < ApplicationRecord
  attribute :country, :string, default: 'ID'
end
栀梦 2024-07-17 06:12:24

在rails 3中使用default_scope

api doc

ActiveRecord 遮盖了数据库(架构)中定义的默认值和应用程序(模型)中完成的默认值之间的区别。 在初始化期间,它解析数据库模式并记录其中指定的任何默认值。 稍后,在创建对象时,它会分配这些架构指定的默认值,而无需接触数据库。

讨论

use default_scope in rails 3

api doc

ActiveRecord obscures the difference between defaulting defined in the database (schema) and defaulting done in the application (model). During initialization, it parses the database schema and notes any default values specified there. Later, when creating objects, it assigns those schema-specified default values without touching the database.

discussion

安穩 2024-07-17 06:12:24

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

令人惊讶的是这里没有建议他

From the api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Use the before_validation method in your model, it gives you the options of creating specific initialisation for create and update calls
e.g. in this example (again code taken from the api docs example) the number field is initialised for a credit card. You can easily adapt this to set whatever values you want

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

Surprised that his has not been suggested here

醉南桥 2024-07-17 06:12:23

每种可用方法都存在几个问题,但我相信定义 after_initialize 回调是可行的方法,原因如下:

  1. default_scope 将为新模型初始化值,但这将成为您找到模型的范围。 如果您只想将某些数字初始化为 0,那么这不是您想要的。
  2. 在迁移中定义默认值也是可行的(在旧版本的 Rails 中,当您仅调用 Model.new 时,这将不起作用)。
  3. 重写initialize可以工作,但不要忘记调用super
  4. 使用像 phusion 这样的插件变得有点荒谬。 这是 ruby​​,我们真的需要一个插件来初始化一些默认值吗?
  5. 从 Rails 3 开始,覆盖 after_initialize 已被弃用。当我在 Rails 3.0.3 中覆盖 after_initialize 时,我在控制台中收到以下警告:

弃用警告:Base#after_initialize 已弃用,请使用 Base.after_initialize :method 代替。 (从 /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    

现在您只有一个地方可以查找模型的初始化。 我一直在使用这种方法,直到有人想出更好的方法为止。

注意事项:

  1. 对于布尔字段:

    如果 self.bool_field.nil,self.bool_field = true?

    有关更多详细信息,请参阅 Paul Russell 对此答案的评论

  2. 如果您仅为模型选择列的子集(即;在像 Person.select(:firstname, :lastname).all 这样的查询中使用 select),如果您的init 方法访问未包含在 select 子句中的列。 您可以像这样防范这种情况:

    self.number ||= 0.0 如果 self.has_attribute? :数字

    对于布尔列...

    self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?

    另请注意,Rails 3.2 之前的语法有所不同(请参阅下面 Cliff Darling 的评论)

There are several issues with each of the available methods, but I believe that defining an after_initialize callback is the way to go for the following reasons:

  1. 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.
  2. Defining defaults in your migration also works (in older versions of Rails this will not work when you just call Model.new).
  3. Overriding initialize can work, but don't forget to call super!
  4. 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?
  5. 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:

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)

Therefore I'd say write an after_initialize callback, which lets you default attributes in addition to letting you set defaults on associations like so:

  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 one place to look for initialization of your models. I'm using this method until someone comes up with a better one.

Caveats:

  1. For boolean fields do:

    self.bool_field = true if self.bool_field.nil?

    See Paul Russell's comment on this answer for more details

  2. 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)

身边 2024-07-17 06:12:23

Rails 5+

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

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

您还可以将 lambda 传递给 default 参数。 示例:

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

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

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

Rails 5+

You can use the attribute method within your models, eg.:

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

You can also pass a lambda to the default parameter. Example:

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

The second argument is the type and it can also be a custom type class instance, for example:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
旧情勿念 2024-07-17 06:12:23

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

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

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

We put the default values in the database through migrations (by specifying the :default option on each column definition) and let Active Record use these values to set the default for each attribute.

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.

Note that the defaults are still in the application (Ruby) code, though not in the model but in the migration(s).

垂暮老矣 2024-07-17 06:12:23

一些简单的情况可以通过在数据库模式中定义默认值来处理,但这不能处理许多更棘手的情况,包括计算值和其他模型的键。 对于这些情况,我这样做:

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

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.

Having seen Brad Murray's answer this is even cleaner if the condition is moved to callback request:

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
叫思念不要吵 2024-07-17 06:12:23

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

The after_initialize callback pattern can be improved by simply doing the following

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.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end
画骨成沙 2024-07-17 06:12:23

Phusion 团队为此提供了一些不错的插件

The Phusion guys have some nice plugin for this.

转身泪倾城 2024-07-17 06:12:23

Rails 6.1+

您现在可以使用属性方法在您的模型上而不设置类型。

attribute :status, default: ACTIVE

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

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

after_initialize :do_something_that_references_instance_or_associations, if: :new_record?

Rails 6.1+

You can now use the attribute method on your model without setting a type.

attribute :status, default: ACTIVE

or

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

Note that feeding a default to attribute cannot reference the instance of the class (a lambda will execute in the context of the class, not the instance). So, if you need to set the default to a value dynamically based on the instance or associations, you're still going to have to use an alternative, such as an after_initialize callback. As stated previously, it's recommended to limit this to new records only to avoid n+1 queries if you reference associations.

after_initialize :do_something_that_references_instance_or_associations, if: :new_record?
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文