Rails3:克隆已经验证的对象可以防止克隆失效——这是奇怪还是正常?

发布于 2024-09-24 20:37:58 字数 868 浏览 1 评论 0原文

在 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

In Rails (3.0) test code, I've cloned an object so I can clobber it for validation testing without changing the original. If I have called assert(original.valid?) before cloning, then the clone passes the validates_presence_of test even after I have set member_id value to nil.

The two tests below illustrate this. In test one, the clone is created before the original ("contact") is validated. Clone correctly fails the validation when member_id is missing. Assertion C succeeds.

In test two, the clone is created after the original is validated. Even though clone.member_id is set to nil, it passes the validation. In other words, assertion 2C fails. The only difference between the tests is the order of the two lines:

  cloned = contact.clone
  assert(contact.valid?,"A")

What is going on here? Is this normal Ruby behavior re: cloning that I just don't understand?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

[旋木] 2024-10-01 20:37:59

你会惊讶地发现——它行不通!

好的,原因可以在 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 实例 - 因为它不为空。就是这样!不是传递“克隆”自我,而是使用较旧的自我。

稍后在验证代码中,Errors 类运行从 @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 中的验证如何工作。

You wil be surprised - it cannot work!

Ok the reason can be found in the Rails code. First validation will run the code:

# Validations module

# Returns the Errors object that holds all information about 
# attribute error messages.
def errors
  @errors ||= Errors.new(self)
end

As it is a first run then it will create new instance of Errors class. Simple, isn't it? But there is a gotcha - the parameter is self. In your case it is "contact" object.

Later then when you call this again on cloned object, the @errors instance will not be created again - as it is not null. And there it is! Instead of passing "cloned" self, the older self is used.

Later in the validation code there the Errors class runs the code that read the value from @base which is the self from the initialization. Can you see it? The values for test are read from original model not from the clone! So the validation on "cloned" object runs on values from the original.

Ok, so far for the "why not" and now a few words about "how to".

The solution is simple - just set @errors to nil after cloning and before validation. As it is quite private, the simple assignment doesn't work. But this works:

cloned.instance_eval do
  @errors = nil
end

And some tip for interesting reading: http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/

It is quite comprehensive explanation how the validations in Rails 3 works.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文