diff --git a/lib/closure_tree/hierarchy_maintenance.rb b/lib/closure_tree/hierarchy_maintenance.rb index e9fe761e..99cafd9f 100644 --- a/lib/closure_tree/hierarchy_maintenance.rb +++ b/lib/closure_tree/hierarchy_maintenance.rb @@ -112,11 +112,28 @@ module ClassMethods # Note that the hierarchy table will be truncated. def rebuild! _ct.with_advisory_lock do - hierarchy_class.delete_all # not destroy_all -- we just want a simple truncate. + cleanup! roots.find_each { |n| n.send(:rebuild!) } # roots just uses the parent_id column, so this is safe. end nil end + + def cleanup! + hierarchy_table = hierarchy_class.arel_table + + [:descendant_id, :ancestor_id].each do |foreign_key| + alias_name = foreign_key.to_s.split('_').first + "s" + alias_table = Arel::Table.new(table_name).alias(alias_name) + arel_join = hierarchy_table.join(alias_table, Arel::Nodes::OuterJoin) + .on(alias_table[primary_key].eq(hierarchy_table[foreign_key])) + .join_sources + + lonely_childs = hierarchy_class.joins(arel_join).where(alias_table[primary_key].eq(nil)) + ids = lonely_childs.pluck(foreign_key) + + hierarchy_class.where(hierarchy_table[foreign_key].in(ids)).delete_all + end + end end end end diff --git a/spec/db/schema.rb b/spec/db/schema.rb index 18d3f4fd..ec8cff72 100644 --- a/spec/db/schema.rb +++ b/spec/db/schema.rb @@ -143,9 +143,6 @@ t.integer "generations", :null => false end - add_foreign_key(:metal_hierarchies, :metal, :column => 'ancestor_id') - add_foreign_key(:metal_hierarchies, :metal, :column => 'descendant_id') - create_table 'menu_items' do |t| t.string 'name' t.integer 'parent_id' diff --git a/spec/hierarchy_maintenance_spec.rb b/spec/hierarchy_maintenance_spec.rb index 6c9b8f60..38794e35 100644 --- a/spec/hierarchy_maintenance_spec.rb +++ b/spec/hierarchy_maintenance_spec.rb @@ -13,4 +13,43 @@ expect(MetalHierarchy.count).to eq(hierarchy_count) end end + + describe '.cleanup!' do + let!(:parent) { Metal.create(:value => "parent metal") } + let!(:child) { Metal.create(:value => "child metal", parent: parent) } + + before do + MetalHierarchy.delete_all + Metal.rebuild! + end + + context 'when an element is deleted' do + it 'should delete the child hierarchies' do + child.delete + + Metal.cleanup! + + expect(MetalHierarchy.where(descendant_id: child.id)).to be_empty + expect(MetalHierarchy.where(ancestor_id: child.id)).to be_empty + end + + it 'should not delete the parent hierarchies' do + child.delete + Metal.cleanup! + expect(MetalHierarchy.where(ancestor_id: parent.id).size).to eq 1 + end + + it 'should not delete other hierarchies' do + other_parent = Metal.create(:value => "other parent metal") + other_child = Metal.create(:value => "other child metal", parent: other_parent) + Metal.rebuild! + + child.delete + Metal.cleanup! + + expect(MetalHierarchy.where(ancestor_id: other_parent.id).size).to eq 2 + expect(MetalHierarchy.where(descendant_id: other_child.id).size).to eq 2 + end + end + end end