diff --git a/lib/closure_tree/finders.rb b/lib/closure_tree/finders.rb index 86626ebb..85f9ce19 100644 --- a/lib/closure_tree/finders.rb +++ b/lib/closure_tree/finders.rb @@ -90,6 +90,15 @@ def with_ancestor(*ancestors) _ct.scope_with_order(scope) end + def with_descendant(*descendants) + descendant_ids = descendants.map { |ea| ea.is_a?(ActiveRecord::Base) ? ea._ct_id : ea } + scope = descendant_ids.blank? ? all : joins(:descendant_hierarchies). + where("#{_ct.hierarchy_table_name}.descendant_id" => descendant_ids). + where("#{_ct.hierarchy_table_name}.generations > 0"). + readonly(false) + _ct.scope_with_order(scope) + end + def find_all_by_generation(generation_level) s = joins(<<-SQL.strip_heredoc) INNER JOIN ( diff --git a/spec/tag_examples.rb b/spec/tag_examples.rb index 8f8ec7e2..d0ba7329 100644 --- a/spec/tag_examples.rb +++ b/spec/tag_examples.rb @@ -417,6 +417,35 @@ def assert_parent_and_children end end + context 'with_descendant' do + it 'works with no rows' do + expect(tag_class.with_descendant.to_a).to be_empty + end + + it 'finds only parents' do + c = tag_class.find_or_create_by_path %w(A B C) + a, b = c.parent.parent, c.parent + spurious_tags = tag_class.find_or_create_by_path %w(D E) + expect(tag_class.with_descendant(c).to_a).to eq([a, b]) + end + + it 'limits subsequent where clauses' do + ac1 = tag_class.create(name: 'A') + ac2 = tag_class.create(name: 'A') + + c1 = tag_class.find_or_create_by_path %w(B C1) + ac1.children << c1.parent + + c2 = tag_class.find_or_create_by_path %w(B C2) + ac2.children << c2.parent + + # different paths! + expect(ac1).not_to eq(ac2) + expect(tag_class.where(:name => 'A').to_a).to match_array([ac1, ac2]) + expect(tag_class.with_descendant(c1).where(:name => 'A').to_a).to eq([ac1]) + end + end + context 'paths' do context 'with grandchild' do before do