update_attributes 即使验证失败也会更改属性

update_attributes changes attributes even if validation fails

例如,如果我 运行 test.update_attributes prop1: 'test', prop2: 'test2'prop1prop2 有防止这些值的验证时,test.prop1 仍然是 'test'test.prop2 仍然是 'test2'。为什么会发生这种情况,我该如何解决?

根据the Rails docs for update_attributes, it's an alias of update。其来源如下:

# File activerecord/lib/active_record/persistence.rb, line 247
def update(attributes)
  # The following transaction covers any possible database side-effects of the
  # attributes assignment. For example, setting the IDs of a child collection.
  with_transaction_returning_status do
    assign_attributes(attributes)
    save
  end
end

因此,它被包装在数据库事务中,这就是回滚发生的原因。但是,让我们看看 assign_attributes。根据its source:

# File activerecord/lib/active_record/attribute_assignment.rb, line 23
def assign_attributes(new_attributes)
  ...
  _assign_attribute(k, v)
  ...
end

那个is defined as:

# File activerecord/lib/active_record/attribute_assignment.rb, line 53
def _assign_attribute(k, v)
  public_send("#{k}=", v)
rescue NoMethodError
  if respond_to?("#{k}=")
    raise
  else
    raise UnknownAttributeError.new(self, k)
  end
end

因此,当您调用 test.update_attributes prop1: 'test', prop2: 'test' 时,它基本上可以归结为:

test.prop1 = 'test'
test.prop2 = 'test'
test.save

如果 save 未通过验证,我们在内存中的 test 副本仍然具有修改后的 prop1prop2 值。因此,我们需要使用 test.reload 并且问题已解决(即我们的数据库和内存版本均未更改)。

tl;drupdate_attributes 调用失败后使用 test.reload

尝试将其包装在 if 语句中:

if test.update(test_params)
  # your code here
else
  # your code here  
end

这是按设计工作的。例如 update 控制器方法通常如下所示:

def update
  @test = Test.find(params[:id])

  if @test.update(test_attributes)
    # redirect to index with success messsage
  else
    render :edit
  end

  private

  def test_attributes
    # params.require here
  end
end

然后 render :edit 将重新显示带有错误消息的表单,并填写不正确的值以供用户更正。所以您实际上确实希望在模型实例中提供不正确的值。