acts_as_tree 不会破坏模型的子级

发布于 2024-07-06 21:30:00 字数 1394 浏览 6 评论 0原文

我有这个任务模型:

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_pa​​rent_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 技术交流群。

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

发布评论

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

评论(2

寂寞清仓 2024-07-13 21:30:00

我发现了这个错误。 该行

d1 = create_task(:parent_id => @root.id, :sort_order => 2)

创建 d1。 这将调用 before_save 回调,该回调又调用 self.children。 正如 Orion 指出的,这会缓存 d1 的子级。

然而,此时 d1 还没有任何子级。 所以 d1 的子缓存是空的。

因此,当我尝试销毁 d1 时,程序会尝试销毁 d1 的子级。 它遇到缓存,发现它是空的,结果并没有破坏d2、d3和d4。

我通过更改任务创建来解决这个问题,如下所示:

@root.children << (d1 = new_task(:sort_order => 2))
@root.save!

这有效,所以我对此表示同意:)我认为也可以通过重新加载 d1 (d1.reload) 或 self 来解决此问题。 Children (self.children(true)) 虽然我没有尝试任何这些解决方案。

I found the bug. The line

d1 = create_task(:parent_id => @root.id, :sort_order => 2)

creates d1. This calls the before_save callback, which in turn calls self.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:

@root.children << (d1 = new_task(:sort_order => 2))
@root.save!

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.

素衣风尘叹 2024-07-13 21:30:00

children 是一个简单的 has_many 关联

这意味着,当您调用 .children 时,它将从数据库加载它们(如果尚不存在)。 然后它将缓存它们。

我想说的是,您的第二个“测试”实际上将查看缓存的值而不是真正的数据库,但这不应该发生,因为您只是使用 Task.count 而不是 d1.children.count。 Hrm

你看过日志吗? 他们将向您显示正在执行的 SQL。 你可能会在那里看到一个 mysql 错误,它会告诉你发生了什么

children is a simple has_many association

This 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 than d1.children.count. Hrm

Have 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

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