diff --git a/README.rdoc b/README.rdoc index e59a924f..bba8f74d 100644 --- a/README.rdoc +++ b/README.rdoc @@ -74,6 +74,7 @@ The has_ancestry methods supports the following options: :destroy All children are destroyed as well (default) :rootify The children of the destroyed node become root nodes :restrict An AncestryException is raised if any children exist + :parentify The orphan subtree is added to the parent of the deleted node.If the deleted node is Root, then rootify the orphan subtree. :cache_depth Cache the depth of each node in the 'ancestry_depth' column (default: false) If you turn depth_caching on for an existing model: - Migrate: add_column [table], :ancestry_depth, :integer, :default => 0 diff --git a/lib/ancestry/class_methods.rb b/lib/ancestry/class_methods.rb index b326c284..c29bd740 100644 --- a/lib/ancestry/class_methods.rb +++ b/lib/ancestry/class_methods.rb @@ -19,11 +19,11 @@ def scope_depth depth_options, depth # Orphan strategy writer def orphan_strategy= orphan_strategy - # Check value of orphan strategy, only rootify, restrict or destroy is allowed - if [:rootify, :restrict, :destroy].include? orphan_strategy + # Check value of orphan strategy, only rootify, adopt, restrict or destroy is allowed + if [:rootify, :adopt, :restrict, :destroy].include? orphan_strategy class_variable_set :@@orphan_strategy, orphan_strategy else - raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify, :restrict and :destroy.") + raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify,:adopt, :restrict and :destroy.") end end diff --git a/lib/ancestry/instance_methods.rb b/lib/ancestry/instance_methods.rb index 5538b5cf..4dfda118 100644 --- a/lib/ancestry/instance_methods.rb +++ b/lib/ancestry/instance_methods.rb @@ -27,7 +27,7 @@ def update_descendants_with_new_ancestry end end end - + # Apply orphan strategy def apply_orphan_strategy # Skip this if callbacks are disabled @@ -48,6 +48,14 @@ def apply_orphan_strategy descendant.destroy end end + # ... make child elements of this node, child of its parent if orphan strategy is adopt + elsif self.base_class.orphan_strategy == :adopt + descendants.all.each do |descendant| + descendant.without_ancestry_callbacks do + new_ancestry = descendant.ancestor_ids.delete_if { |x| x == self.id }.join("/") + descendant.update_attribute descendant.class.ancestry_column, new_ancestry || nil + end + end # ... throw an exception if it has children and orphan strategy is restrict elsif self.base_class.orphan_strategy == :restrict raise Ancestry::AncestryException.new('Cannot delete record because it has descendants.') unless is_childless? @@ -158,7 +166,7 @@ def siblings end def sibling_ids - siblings.all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym) + siblings.all(:select => self.base_class.primary_key).collect(&self.base_class.primary_key.to_sym) end def has_siblings? @@ -219,7 +227,6 @@ def cast_primary_key(key) def primary_key_type @primary_key_type ||= column_for_attribute(self.class.primary_key).type end - def unscoped_descendants self.base_class.unscoped do self.base_class.all(:conditions => descendant_conditions) diff --git a/test/has_ancestry_test.rb b/test/has_ancestry_test.rb index 0fb805af..30f9b706 100644 --- a/test/has_ancestry_test.rb +++ b/test/has_ancestry_test.rb @@ -1,6 +1,7 @@ require "environment" class HasAncestryTreeTest < ActiveSupport::TestCase + def test_default_ancestry_column AncestryTestDatabase.with_model do |model| assert_equal :ancestry, model.ancestry_column @@ -302,6 +303,23 @@ def test_orphan_restrict_strategy end end end + + def test_orphan_adopt_strategy + AncestryTestDatabase.with_model do |model| + model.orphan_strategy = :adopt # set the orphan strategy as paerntify + n1 = model.create! #create a root node + n2 = model.create!(:parent => n1) #create child with parent=root + n3 = model.create!(:parent => n2) #create child with parent=n2, depth = 2 + n4 = model.create!(:parent => n2) #create child with parent=n2, depth = 2 + n5 = model.create!(:parent => n4) #create child with parent=n4, depth = 3 + n2.destroy # delete a node with desecendants + assert_equal(model.find(n3.id).parent,n1, "orphan's not parentified" ) + assert_equal(model.find(n5.id).ancestor_ids,[n1.id,n4.id], "ancestry integrity not maintained") + n1.destroy # delete a root node with desecendants + assert_equal(model.find(n3.id).parent_id,nil," Children of the deleted root not rootfied") + assert_equal(model.find(n5.id).ancestor_ids,[n4.id],"ancestry integrity not maintained") + end + end def test_integrity_checking AncestryTestDatabase.with_model :width => 3, :depth => 3 do |model, roots|