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?
I found the bug. The line
creates d1. This calls the
before_savecallback, 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.