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


当前回答

我遇到了问题after_initialize给出ActiveModel::MissingAttributeError错误时做复杂的发现:

eg:

@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调用是必要的,以确保在执行自定义代码(即:default_values)之前从ActiveRecord::Base正确初始化对象

其他回答

after_initialize解决方案的问题是,无论是否访问该属性,都必须向从DB中查找的每个对象添加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')。在这种情况下,我认为您确实需要在数据库约束中设置默认值,如果数据库支持它的话。

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

最佳回答:这取决于你想要什么!

如果你想让每个对象都以一个值开始:使用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

我已经用了一段时间了。

# post.rb
class Post < ApplicationRecord
  attribute :country, :string, default: '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?

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

以下是我的解决方法:

我有一个用户表和一个角色表。Users表属于Roles表。我也有一个管理员和学生模型,从用户表继承。

然后需要在创建用户时为角色设置一个默认值,比如id = 1的管理员角色或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?

但是第一种方法更简洁,也更精确。

这是所有。

我希望这对你们有帮助