assign_attributes 不覆盖 not nil 数据

assign_attributes without overwriting not nil data

有没有属性覆盖的 assign_attributes 等价物?

让我用一个简单的例子来解释一下:

my_model = MyModel.new(first_name: 'Romain', age: nil)

new_attributes = {first_name: 'Pierre', last_name: 'Roger', age: 27}
my_model.assign_attributes(new_attributes)
# What I get : <first_name='Pierre', last_name='Roger', age=27>
#  * Romain is overwritten by Pierre
#  * nil is overwritten by 27
# What I would like : <first_name='Romain', last_name='Roger', age=27>
#  * Romain isn't overwritten by Pierre
#  * nil is overwritten by 27

我知道我可以做这样的事情,但它似乎不对:

new_attributes.merge(
    my_model.slice(:first_name, :last_name, :age)
            .select { |_, val| !val.nil? }
)
my_model.assign_attributes(new_attributes)

有什么想法吗?

这对你有用吗?

my_model = MyModel.new(first_name: 'Romain', age: nil)
new_attributes = {first_name: 'Pierre', last_name: 'Roger', age: 27}

my_model.assign_attributes(my_model.attributes.reject{|k,v|v.nil?}.reverse_merge(new_attributes))

如评论中所述,我认为 ActiveRecord 中没有现成的方法来执行此操作。但是,您可以自己编写,甚至更好 - 修改现有的 assign_attributes 方法。当然,我们需要格外小心,不要破坏它的基本功能 - 我们只会稍微扩展它。

assign_attributes方法在ActiveRecord::AttributeAssignment模块中定义,然后包含在ActiveRecord::Base中。我们可以直接在它的模块中覆盖它,但编写一个新模块并将其包含在 ActiveRecord::Base 中似乎更干净(因为我们可以调用 super,而不是别名链接)。将以下代码复制到 config/initializers 中的新文件(任何名称都可以):

module ActiveRecord::AttributeAssignmentOverride
  def assign_attributes(new_attributes, options={})
    return super(new_attributes) if options.fetch(:override, true)
    super(new_attributes.select {|k,_| self[k].nil? })
  end
end

class ActiveRecord::Base
  include ActiveRecord::AttributeAssignmentOverride
end

然后就可以用assign_attributes正常的方式(重启后自然):

model.assign_attributes(new_params)

它将照常工作,覆盖所有非零值。但是,您现在可以添加额外的选项:

 model.assign_attributes(new_params, override: false)

不会触及已分配的值。

陷阱

上面的代码仅适用于 rails 4。Rails 3 使用 attr_accessible 而不是强参数来保证批量分配安全。这些可能取决于许多条件,因此在 rails 3 中,默认 assign_attributes 方法接受两个参数(与我们的新参数相同)。这意味着您还需要将选项传递给对 super.

的每次调用