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


当前回答

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

其他回答

一个比建议的答案更好/更干净的潜在方法是覆盖访问器,像这样:

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

请参阅ActiveRecord::Base文档中的“覆盖默认访问器”和StackOverflow中关于使用self的更多信息。

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

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

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

在开发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?

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

这是所有。

我希望这对你们有帮助

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