acts_as_tree 不会破坏模型的子级
我有这个任务模型:
class Task < ActiveRecord::Base
acts_as_tree :order => 'sort_order'
end
我有这个测试
class TaskTest < Test::Unit::TestCase
def setup
@root = create_root
end
def test_destroying_a_task_should_destroy_all_of_its_descendants
d1 = create_task(:parent_id => @root.id, :sort_order => 2)
d2 = create_task(:parent_id => d1.id, :sort_order => 3)
d3 = create_task(:parent_id => d2.id, :sort_order => 4)
d4 = create_task(:parent_id => d1.id, :sort_order => 5)
assert_equal 5, Task.count
d1.destroy
assert_equal @root, Task.find(:first)
assert_equal 1, Task.count
end
end
测试成功:当我销毁 d1 时,它会销毁 d1 的所有后代。 因此,销毁后仅留下根。
但是,在我向任务添加 before_save 回调后,此测试现在失败。 这是我添加到任务中的代码:
before_save :update_descendants_if_necessary
def update_descendants_if_necessary
handle_parent_id_change if self.parent_id_changed?
return true
end
def handle_parent_id_change
self.children.each do |sub_task|
#the code within the loop is deliberately commented out
end
end
当我添加此代码时,assert_equal 1, Task.count
失败,Task.count == 4
。 我认为 handled_parent_id_change
下的 self.children
是罪魁祸首,因为当我注释掉 self.children.each do |sub_task|
块时,测试再次通过。
有任何想法吗?
I have this Task model:
class Task < ActiveRecord::Base
acts_as_tree :order => 'sort_order'
end
And I have this test
class TaskTest < Test::Unit::TestCase
def setup
@root = create_root
end
def test_destroying_a_task_should_destroy_all_of_its_descendants
d1 = create_task(:parent_id => @root.id, :sort_order => 2)
d2 = create_task(:parent_id => d1.id, :sort_order => 3)
d3 = create_task(:parent_id => d2.id, :sort_order => 4)
d4 = create_task(:parent_id => d1.id, :sort_order => 5)
assert_equal 5, Task.count
d1.destroy
assert_equal @root, Task.find(:first)
assert_equal 1, Task.count
end
end
The test is successful: when I destroy d1, it destroys all the descendants of d1. Thus, after the destroy only the root is left.
However, this test is now failing after I have added a before_save callback to the Task. This is the code I added to Task:
before_save :update_descendants_if_necessary
def update_descendants_if_necessary
handle_parent_id_change if self.parent_id_changed?
return true
end
def handle_parent_id_change
self.children.each do |sub_task|
#the code within the loop is deliberately commented out
end
end
When I added this code, assert_equal 1, Task.count
fails, with Task.count == 4
. I think self.children
under handled_parent_id_change
is the culprit, because when I comment out the self.children.each do |sub_task|
block, the test passes again.
Any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我发现了这个错误。 该行
创建 d1。 这将调用
before_save
回调,该回调又调用self.children
。 正如 Orion 指出的,这会缓存 d1 的子级。然而,此时 d1 还没有任何子级。 所以 d1 的子缓存是空的。
因此,当我尝试销毁 d1 时,程序会尝试销毁 d1 的子级。 它遇到缓存,发现它是空的,结果并没有破坏d2、d3和d4。
我通过更改任务创建来解决这个问题,如下所示:
这有效,所以我对此表示同意:)我认为也可以通过重新加载 d1 (
d1.reload
) 或 self 来解决此问题。 Children (self.children(true)
) 虽然我没有尝试任何这些解决方案。I found the bug. The line
creates d1. This calls the
before_save
callback, which in turn callsself.children
. As Orion pointed out, this caches the children of d1.However, at this point, d1 doesn't have any children yet. So d1's cache of children is empty.
Thus, when I try to destroy d1, the program tries to destroy d1's children. It encounters the cache, finds that it is empty, and a result doesn't destroy d2, d3, and d4.
I solved this by changing the task creations like this:
This worked so I'm ok with it :) I think it is also possible to fix this by either reloading d1 (
d1.reload
) or self.children (self.children(true)
) although I didn't try any of these solutions.children
是一个简单的 has_many 关联这意味着,当您调用
.children
时,它将从数据库加载它们(如果尚不存在)。 然后它将缓存它们。我想说的是,您的第二个“测试”实际上将查看缓存的值而不是真正的数据库,但这不应该发生,因为您只是使用
Task.count
而不是d1.children.count
。 Hrm你看过日志吗? 他们将向您显示正在执行的 SQL。 你可能会在那里看到一个 mysql 错误,它会告诉你发生了什么
children
is a simple has_many associationThis means, when you call
.children
, it will load them from the database (if not already present). It will then cache them.I was going to say that your second 'test' will actually be looking at the cached values not the real database, but that shouldn't happen as you are just using
Task.count
rather thand1.children.count
. HrmHave you looked at the logs? They will show you the SQL which is being executed. You may see a mysql error in there which will tell you what's going on