在 Rails (3.0) 测试代码中,我克隆了一个对象,这样我就可以在不更改原始对象的情况下破坏它进行验证测试。如果我在克隆之前调用了assert(original.valid?),那么即使我将member_id 值设置为nil,克隆也会通过validates_presence_of 测试。
下面的两个测试说明了这一点。在测试一中,克隆是在验证原始(“联系人”)之前创建的。当 member_id 丢失时,克隆正确地导致验证失败。断言 C 成功。
在测试二中,克隆是在原始版本验证后创建的。即使clone.member_id设置为nil,它也通过验证。换句话说,断言 2C 失败。测试之间的唯一区别是两行的顺序:
cloned = contact.clone
assert(contact.valid?,"A")
这是怎么回事?这是正常的 Ruby 行为吗:我只是不明白克隆?
test "clone problem 1" do
contact = Contact.new(:member_id => 1)
cloned = contact.clone
assert(contact.valid?,"A")
cloned.member_id = nil
assert(!cloned.valid?,"C")
end
test "clone problem 2" do
contact = Contact.new(:member_id => 1)
assert(contact.valid?,"2A")
cloned = contact.clone
cloned.member_id = nil
assert(!cloned.valid?,"2C")
end
最佳答案
你会感到惊讶 - 它不起作用!
好的,原因可以在 Rails 代码中找到。第一次验证将运行代码:
# Validations module
# Returns the Errors object that holds all information about
# attribute error messages.
def errors
@errors ||= Errors.new(self)
end
由于这是第一次运行,因此它将创建 Errors 类的新实例。很简单,不是吗?但有一个问题——参数是 self。在您的情况下,它是“联系”对象。
稍后,当您在克隆对象上再次调用此方法时,将不会再次创建 @errors 实例 - 因为它不为空。就是这样!使用较旧的 self ,而不是传递“克隆” self 。
稍后在验证代码中,Errors 类运行从@base 读取值的代码,@base 是初始化时的self。你能看见它吗?测试值是从原始模型而不是克隆模型中读取的!因此,对“克隆”对象的验证基于原始值运行。
好的,到目前为止“为什么不”,现在谈谈“如何做”。
解决方案很简单 - 只需在克隆之后和验证之前将 @errors 设置为 nil 即可。由于它非常私密,简单的分配不起作用。但这是有效的:
cloned.instance_eval do
@errors = nil
end
还有一些有趣的阅读提示:http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
它非常全面地解释了 Rails 3 中的验证如何工作。
关于ruby-on-rails - rails 3 : Cloning already-validated object prevents clone being invalidated -- is this strange or normal?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3800129/