require 'spec_helper' shared_examples_for "Tag (without fixtures)" do let (:tag_class) { described_class } let (:tag_hierarchy_class) { described_class.hierarchy_class } context 'class setup' do it 'has correct accessible_attributes' do if tag_class._ct.use_attr_accessible? tag_class.accessible_attributes.to_a.should =~ %w(parent name title) end end it 'should build hierarchy classname correctly' do tag_class.hierarchy_class.should == tag_hierarchy_class tag_class._ct.hierarchy_class_name.should == tag_hierarchy_class.to_s tag_class._ct.short_hierarchy_class_name.should == tag_hierarchy_class.to_s end it 'should have a correct parent column name' do expected_parent_column_name = tag_class == UUIDTag ? "parent_uuid" : "parent_id" tag_class._ct.parent_column_name.should == expected_parent_column_name end end describe "from empty db" do def nuke_db tag_hierarchy_class.delete_all tag_class.delete_all end before :each do nuke_db end context "with no tags" do it "should return no entities" do tag_class.roots.should be_empty tag_class.leaves.should be_empty end end context "with 1 tag" do it "should return the only entity as a root and leaf" do a = tag_class.create!(:name => "a") tag_class.roots.should == [a] tag_class.leaves.should == [a] end end context "with 2 tags" do before :each do @root = tag_class.create!(:name => "root") @leaf = @root.add_child(tag_class.create!(:name => "leaf")) end it "should return a simple root and leaf" do tag_class.roots.should == [@root] tag_class.leaves.should == [@leaf] end it "should return child_ids for root" do @root.child_ids.should == [@leaf.id] end it "should return an empty array for leaves" do @leaf.child_ids.should be_empty end end context "3 tag collection.create db" do before :each do @root = tag_class.create! :name => "root" @mid = @root.children.create! :name => "mid" @leaf = @mid.children.create! :name => "leaf" DestroyedTag.delete_all end it "should create all tags" do tag_class.all.to_a.should =~ [@root, @mid, @leaf] end it "should return a root and leaf without middle tag" do tag_class.roots.should == [@root] tag_class.leaves.should == [@leaf] end it "should delete leaves" do tag_class.leaves.destroy_all tag_class.roots.should == [@root] # untouched tag_class.leaves.should == [@mid] end it "should delete everything if you delete the roots" do tag_class.roots.destroy_all tag_class.all.should be_empty tag_class.roots.should be_empty tag_class.leaves.should be_empty DestroyedTag.all.map { |t| t.name }.should =~ %w{root mid leaf} end end context "3 tag explicit_create db" do before :each do @root = tag_class.create!(:name => "root") @mid = @root.add_child(tag_class.create!(:name => "mid")) @leaf = @mid.add_child(tag_class.create!(:name => "leaf")) end it "should create all tags" do tag_class.all.to_a.should =~ [@root, @mid, @leaf] end it "should return a root and leaf without middle tag" do tag_class.roots.should == [@root] tag_class.leaves.should == [@leaf] end it "should prevent parental loops from torso" do @mid.children << @root @root.valid?.should be_false @mid.reload.children.should == [@leaf] end it "should prevent parental loops from toes" do @leaf.children << @root @root.valid?.should be_false @leaf.reload.children.should be_empty end it "should support re-parenting" do @root.children << @leaf tag_class.leaves.should == [@leaf, @mid] end it "cleans up hierarchy references for leaves" do @leaf.destroy tag_hierarchy_class.where(:ancestor_id => @leaf.id).should be_empty tag_hierarchy_class.where(:descendant_id => @leaf.id).should be_empty end it "cleans up hierarchy references" do @mid.destroy tag_hierarchy_class.where(:ancestor_id => @mid.id).should be_empty tag_hierarchy_class.where(:descendant_id => @mid.id).should be_empty @root.reload.should be_root root_hiers = @root.ancestor_hierarchies.to_a root_hiers.size.should == 1 tag_hierarchy_class.where(:ancestor_id => @root.id).should == root_hiers tag_hierarchy_class.where(:descendant_id => @root.id).should == root_hiers end it "should have different hash codes for each hierarchy model" do hashes = tag_hierarchy_class.all.map(&:hash) hashes.should =~ hashes.uniq end it "should return the same hash code for equal hierarchy models" do tag_hierarchy_class.first.hash.should == tag_hierarchy_class.first.hash end end it "performs as the readme says it does" do grandparent = tag_class.create(:name => 'Grandparent') parent = grandparent.children.create(:name => 'Parent') child1 = tag_class.create(:name => 'First Child', :parent => parent) child2 = tag_class.new(:name => 'Second Child') parent.children << child2 child3 = tag_class.new(:name => 'Third Child') parent.add_child child3 grandparent.self_and_descendants.collect(&:name).should == ["Grandparent", "Parent", "First Child", "Second Child", "Third Child"] child1.ancestry_path.should == ["Grandparent", "Parent", "First Child"] child3.ancestry_path.should == ["Grandparent", "Parent", "Third Child"] d = tag_class.find_or_create_by_path %w(a b c d) h = tag_class.find_or_create_by_path %w(e f g h) e = h.root d.add_child(e) # "d.children << e" would work too, of course h.ancestry_path.should == %w(a b c d e f g h) end it "roots sort alphabetically" do expected = ("a".."z").to_a expected.shuffle.each { |ea| tag_class.create!(:name => ea) } tag_class.roots.collect { |ea| ea.name }.should == expected end context "with simple tree" do before :each do tag_class.find_or_create_by_path %w(a1 b1 c1a) tag_class.find_or_create_by_path %w(a1 b1 c1b) tag_class.find_or_create_by_path %w(a2 b2) tag_class.find_or_create_by_path %w(a3) @a1, @a2, @a3, @b1, @b2, @c1a, @c1b = tag_class.where(:name => %w(a1 a2 a3 b1 b2 c1a c1b)).reorder(:name).to_a @expected_roots = [@a1, @a2, @a3] @expected_leaves = [@c1a, @c1b, @b2, @a3] end it 'should find global roots' do tag_class.roots.to_a.should =~ @expected_roots end it 'should return root? for roots' do @expected_roots.each { |ea| ea.should be_root } end it 'should not return root? for non-roots' do [@b1, @b2, @c1a, @c1b].each { |ea| ea.should_not be_root } end it 'should return the correct root' do {@a1 => @a1, @a2 => @a2, @a3 => @a3, @b1 => @a1, @b2 => @a2, @c1a => @a1, @c1b => @a1}.each do |node, root| node.root.should == root end end it 'should assemble global leaves' do tag_class.leaves.to_a.should =~ @expected_leaves end it 'should assemble instance leaves' do {@a1 => [@c1a, @c1b], @b1 => [@c1a, @c1b], @a2 => [@b2]}.each do |node, leaves| node.leaves.to_a.should == leaves end @expected_leaves.each { |ea| ea.leaves.to_a.should == [ea] } end it 'should return leaf? for leaves' do @expected_leaves.each { |ea| ea.should be_leaf } end end context 'with_ancestor' do it 'works with no rows' do tag_class.with_ancestor().to_a.should be_empty end it 'finds only children' do c = tag_class.find_or_create_by_path %w(A B C) a, b = c.parent.parent, c.parent e = tag_class.find_or_create_by_path %w(D E) tag_class.with_ancestor(a).to_a.should == [b, c] end it 'limits subsequent where clauses' do a1c = tag_class.find_or_create_by_path %w(A1 B C) a2c = tag_class.find_or_create_by_path %w(A2 B C) tag_class.where(:name => "C").to_a.should =~ [a1c, a2c] tag_class.with_ancestor(a1c.parent.parent).where(:name => "C").to_a.should == [a1c] end end context "paths" do before :each do @child = tag_class.find_or_create_by_path(%w(grandparent parent child)) @child.title = "Kid" @parent = @child.parent @parent.title = "Mom" @grandparent = @parent.parent @grandparent.title = "Nonnie" [@child, @parent, @grandparent].each { |ea| ea.save! } end it "should build ancestry path" do @child.ancestry_path.should == %w{grandparent parent child} @child.ancestry_path(:name).should == %w{grandparent parent child} @child.ancestry_path(:title).should == %w{Nonnie Mom Kid} end it "should find by path" do # class method: tag_class.find_by_path(%w{grandparent parent child}).should == @child # instance method: @parent.find_by_path(%w{child}).should == @child @grandparent.find_by_path(%w{parent child}).should == @child @parent.find_by_path(%w{child larvae}).should be_nil end it "finds correctly rooted paths" do decoy = tag_class.find_or_create_by_path %w(a b c d) b_d = tag_class.find_or_create_by_path %w(b c d) tag_class.find_by_path(%w(b c d)).should == b_d tag_class.find_by_path(%w(c d)).should be_nil end it "find_by_path for 1 node" do b = tag_class.find_or_create_by_path %w(a b) b2 = b.root.find_by_path(%w(b)) b2.should == b end it "find_by_path for 2 nodes" do c = tag_class.find_or_create_by_path %w(a b c) c.root.find_by_path(%w(b c)).should == c c.root.find_by_path(%w(a c)).should be_nil c.root.find_by_path(%w(c)).should be_nil end it "find_by_path for 3 nodes" do d = tag_class.find_or_create_by_path %w(a b c d) d.root.find_by_path(%w(b c d)).should == d tag_class.find_by_path(%w(a b c d)).should == d tag_class.find_by_path(%w(d)).should be_nil end it "should return nil for missing nodes" do tag_class.find_by_path(%w{missing}).should be_nil tag_class.find_by_path(%w{grandparent missing}).should be_nil tag_class.find_by_path(%w{grandparent parent missing}).should be_nil tag_class.find_by_path(%w{grandparent parent missing child}).should be_nil end it "should find or create by path" do # class method: grandparent = tag_class.find_or_create_by_path(%w{grandparent}) grandparent.should == @grandparent child = tag_class.find_or_create_by_path(%w{grandparent parent child}) child.should == @child tag_class.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary} a = tag_class.find_or_create_by_path(%w{a}) a.ancestry_path.should == %w{a} # instance method: a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c} end it "should respect attribute hashes with both selection and creation" do expected_title = 'something else' attrs = {:title => expected_title} existing_title = @grandparent.title new_grandparent = tag_class.find_or_create_by_path(%w{grandparent}, attrs) new_grandparent.should_not == @grandparent new_grandparent.title.should == expected_title @grandparent.reload.title.should == existing_title end it "should create a hierarchy with a given attribute" do expected_title = 'unicorn rainbows' attrs = {:title => expected_title} child = tag_class.find_or_create_by_path(%w{grandparent parent child}, attrs) child.should_not == @child [child, child.parent, child.parent.parent].each do |ea| ea.title.should == expected_title end end end context "hash_tree" do before :each do @b = tag_class.find_or_create_by_path %w(a b) @a = @b.parent @b2 = tag_class.find_or_create_by_path %w(a b2) @d1 = @b.find_or_create_by_path %w(c1 d1) @c1 = @d1.parent @d2 = @b.find_or_create_by_path %w(c2 d2) @c2 = @d2.parent @full_tree = {@a => {@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}, @b2 => {}}} #File.open("example.dot", "w") { |f| f.write(tag_class.root.to_dot_digraph) } end context "#hash_tree" do it "returns {} for depth 0" do tag_class.hash_tree(:limit_depth => 0).should == {} end it "limit_depth 1" do tag_class.hash_tree(:limit_depth => 1).should == {@a => {}} end it "limit_depth 2" do tag_class.hash_tree(:limit_depth => 2).should == {@a => {@b => {}, @b2 => {}}} end it "limit_depth 3" do tag_class.hash_tree(:limit_depth => 3).should == {@a => {@b => {@c1 => {}, @c2 => {}}, @b2 => {}}} end it "limit_depth 4" do tag_class.hash_tree(:limit_depth => 4).should == @full_tree end it "no limit holdum" do tag_class.hash_tree.should == @full_tree end end def assert_no_dupes(scope) # the named scope is complicated enough that an incorrect join could result in unnecessarily # duplicated rows: a = scope.collect { |ea| ea.id } a.should == a.uniq end context "#hash_tree_scope" do it "no dupes for any depth" do (0..5).each do |ea| assert_no_dupes(tag_class.hash_tree_scope(ea)) end end it "no limit holdum" do assert_no_dupes(tag_class.hash_tree_scope) end end context ".hash_tree_scope" do it "no dupes for any depth" do (0..5).each do |ea| assert_no_dupes(@a.hash_tree_scope(ea)) end end it "no limit holdum" do assert_no_dupes(@a.hash_tree_scope) end end context ".hash_tree" do before :each do end it "returns {} for depth 0" do @b.hash_tree(:limit_depth => 0).should == {} end it "limit_depth 1" do @b.hash_tree(:limit_depth => 1).should == {@b => {}} end it "limit_depth 2" do @b.hash_tree(:limit_depth => 2).should == {@b => {@c1 => {}, @c2 => {}}} end it "limit_depth 3" do @b.hash_tree(:limit_depth => 3).should == {@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}} end it "no limit holdum from subsubroot" do @c1.hash_tree.should == {@c1 => {@d1 => {}}} end it "no limit holdum from subroot" do @b.hash_tree.should == {@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}} end it "no limit holdum from root" do @a.hash_tree.should == @full_tree end end end describe 'DOT rendering' do it 'should render for an empty scope' do tag_class.to_dot_digraph(tag_class.where("0=1")).should == "digraph G {\n}\n" end it 'should render for an empty scope' do tag_class.find_or_create_by_path(%w(a b1 c1)) tag_class.find_or_create_by_path(%w(a b2 c2)) tag_class.find_or_create_by_path(%w(a b2 c3)) a, b1, b2, c1, c2, c3 = %w(a b1 b2 c1 c2 c3).map { |ea| tag_class.where(:name => ea).first.id } dot = tag_class.roots.first.to_dot_digraph dot.should == <<-DOT digraph G { #{a} [label="a"] #{a} -> #{b1} #{b1} [label="b1"] #{a} -> #{b2} #{b2} [label="b2"] #{b1} -> #{c1} #{c1} [label="c1"] #{b2} -> #{c2} #{c2} [label="c2"] #{b2} -> #{c3} #{c3} [label="c3"] } DOT end end end end