closure_tree 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "4.0.1" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = Gem::Version.new("4.1.0") unless defined?(::ClosureTree::VERSION)
3
3
  end
@@ -26,17 +26,17 @@ ActiveRecord::Schema.define(:version => 0) do
26
26
  t.integer "generations", :null => false
27
27
  end
28
28
 
29
- create_table "tags_uuid", :id => false, :force => true do |t|
30
- t.string "id", :unique => true
29
+ create_table "uuid_tags", :id => false, :force => true do |t|
30
+ t.string "uuid", :unique => true
31
31
  t.string "name"
32
32
  t.string "title"
33
- t.string "parent_id"
33
+ t.string "parent_uuid"
34
34
  t.integer "sort_order"
35
35
  t.datetime "created_at"
36
36
  t.datetime "updated_at"
37
37
  end
38
38
 
39
- create_table "tag_hierarchies_uuid", :id => false, :force => true do |t|
39
+ create_table "uuid_tag_hierarchies", :id => false, :force => true do |t|
40
40
  t.string "ancestor_id", :null => false
41
41
  t.string "descendant_id", :null => false
42
42
  t.integer "generations", :null => false
@@ -123,10 +123,10 @@ describe Label do
123
123
  end
124
124
  roots = classes.first.roots
125
125
  i = instances.shift
126
- roots.should =~ [i]
126
+ roots.to_a.should =~ [i]
127
127
  while (!instances.empty?) do
128
128
  child = instances.shift
129
- i.children.should =~ [child]
129
+ i.children.to_a.should =~ [child]
130
130
  i = child
131
131
  end
132
132
  end
@@ -353,7 +353,7 @@ describe Label do
353
353
  labels(:c2).append_sibling(labels(:e2))
354
354
  labels(:e2).self_and_siblings.to_a.should == [labels(:b1), labels(:b2), labels(:c2), labels(:e2)]
355
355
  labels(:a1).self_and_descendants.collect(&:name).should == %w(a1 b1 b2 c2 e2 d2 c1-six c1-seven c1-eight c1-nine)
356
- labels(:a1).leaves.collect(&:name).should == %w(b2 e2 d2 c1-six c1-seven c1-eight c1-nine)
356
+ labels(:a1).leaves.collect(&:name).should =~ %w(b2 e2 d2 c1-six c1-seven c1-eight c1-nine)
357
357
  end
358
358
  end
359
359
 
@@ -1,5 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
+ parallelism_is_broken = begin
4
+ # Rails < 3.2 has known bugs with parallelism
5
+ (ActiveRecord::VERSION::MAJOR <= 3 && ActiveRecord::VERSION::MINOR < 2) ||
6
+ # SQLite doesn't support parallel writes
7
+ ENV["DB"] =~ /sqlite/
8
+ end
9
+
3
10
  describe "threadhot" do
4
11
 
5
12
  before :each do
@@ -34,7 +41,7 @@ describe "threadhot" do
34
41
  Tag.roots.collect { |ea| ea.name.to_i }.should =~ @times
35
42
  # No dupe children:
36
43
  %w(a b c).each do |ea|
37
- Tag.find_all_by_name(ea).size.should == @iterations
44
+ Tag.where(:name => ea).size.should == @iterations
38
45
  end
39
46
  end
40
47
 
@@ -42,9 +49,9 @@ describe "threadhot" do
42
49
  @parent = Tag.create!(:name => "root")
43
50
  run_workers
44
51
  @parent.reload.children.collect { |ea| ea.name.to_i }.should =~ @times
45
- Tag.find_all_by_name(@names).size.should == @iterations
52
+ Tag.where(:name => @names).size.should == @iterations
46
53
  %w(a b c).each do |ea|
47
- Tag.find_all_by_name(ea).size.should == @iterations
54
+ Tag.where(:name => ea).size.should == @iterations
48
55
  end
49
56
  end
50
57
 
@@ -52,8 +59,7 @@ describe "threadhot" do
52
59
  # disable with_advisory_lock:
53
60
  Tag.should_receive(:with_advisory_lock).any_number_of_times { |lock_name, &block| block.call }
54
61
  run_workers
55
- Tag.find_all_by_name(@names).size.should > @iterations
62
+ Tag.where(:name => @names).size.should > @iterations
56
63
  end
57
64
 
58
- # SQLite doesn't like parallelism, and Rails 3.0 and 3.1 have known threading issues. SKIP.
59
- end if ((ENV["DB"] != "sqlite") && (ActiveRecord::VERSION::STRING =~ /^3.2/))
65
+ end unless parallelism_is_broken
@@ -2,12 +2,14 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
2
  plugin_test_dir = File.dirname(__FILE__)
3
3
 
4
4
  require 'rubygems'
5
- require 'bundler/setup'
6
-
5
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
6
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
7
7
  require 'rspec'
8
- require 'logger'
9
-
10
- require 'action_controller' # rspec-rails needs this :(
8
+ require 'rails'
9
+ require 'active_record'
10
+ require 'active_record/fixtures'
11
+ require 'rspec/rails/adapters'
12
+ require 'rspec/rails/fixture_support'
11
13
  require 'closure_tree'
12
14
  require 'tmpdir'
13
15
 
@@ -20,12 +22,31 @@ require 'erb'
20
22
  ENV["DB"] ||= "mysql"
21
23
  ActiveRecord::Base.table_name_prefix = ENV['DB_PREFIX'].to_s
22
24
  ActiveRecord::Base.table_name_suffix = ENV['DB_SUFFIX'].to_s
25
+
26
+ if ENV['ATTR_ACCESSIBLE'] == '1'
27
+ # turn on whitelisted attributes:
28
+ ActiveRecord::Base.send(:include, ActiveModel::MassAssignmentSecurity)
29
+ end
30
+
23
31
  ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(plugin_test_dir + "/db/database.yml")).result)
24
32
  ActiveRecord::Base.establish_connection(ENV["DB"])
25
33
  ActiveRecord::Migration.verbose = false
26
34
  require 'db/schema'
27
35
  require 'support/models'
28
- require 'rspec/rails' # TODO: clean this up-- I don't want to pull the elephant through the mouse hole just for fixture support
36
+
37
+ class Hash
38
+ def render_from_yield(&block)
39
+ inject({}) do |h, entry|
40
+ k, v = entry
41
+ h[block.call(k)] = if v.is_a?(Hash) then
42
+ v.render_from_yield(&block)
43
+ else
44
+ block.call(v)
45
+ end
46
+ h
47
+ end
48
+ end
49
+ end
29
50
 
30
51
  DB_QUERIES = []
31
52
 
@@ -3,7 +3,26 @@ require 'uuidtools'
3
3
  class Tag < ActiveRecord::Base
4
4
  acts_as_tree :dependent => :destroy, :order => "name"
5
5
  before_destroy :add_destroyed_tag
6
- attr_accessible :name
6
+ attr_accessible :name if _ct.use_attr_accessible?
7
+ def to_s
8
+ name
9
+ end
10
+ def add_destroyed_tag
11
+ # Proof for the tests that the destroy rather than the delete method was called:
12
+ DestroyedTag.create(:name => name)
13
+ end
14
+ end
15
+
16
+ class UUIDTag < ActiveRecord::Base
17
+ self.primary_key = :uuid
18
+ before_create :set_uuid
19
+ acts_as_tree :dependent => :destroy, :order => 'name', :parent_column_name => 'parent_uuid'
20
+ before_destroy :add_destroyed_tag
21
+ attr_accessible :name if _ct.use_attr_accessible?
22
+
23
+ def set_uuid
24
+ self.uuid = UUIDTools::UUID.timestamp_create.to_s
25
+ end
7
26
 
8
27
  def to_s
9
28
  name
@@ -15,8 +34,10 @@ class Tag < ActiveRecord::Base
15
34
  end
16
35
  end
17
36
 
37
+ USE_ATTR_ACCESSIBLE = Tag._ct.use_attr_accessible?
38
+
18
39
  class DestroyedTag < ActiveRecord::Base
19
- attr_accessible :name
40
+ attr_accessible :name if USE_ATTR_ACCESSIBLE
20
41
  end
21
42
 
22
43
  class User < ActiveRecord::Base
@@ -31,7 +52,7 @@ class User < ActiveRecord::Base
31
52
  Contract.where(:user_id => descendant_ids)
32
53
  end
33
54
 
34
- attr_accessible :email, :referrer
55
+ attr_accessible :email, :referrer if USE_ATTR_ACCESSIBLE
35
56
 
36
57
  def to_s
37
58
  email
@@ -43,7 +64,8 @@ class Contract < ActiveRecord::Base
43
64
  end
44
65
 
45
66
  class Label < ActiveRecord::Base
46
- attr_accessible :name # <- make sure order doesn't matter
67
+ # make sure order doesn't matter
68
+ attr_accessible :name if USE_ATTR_ACCESSIBLE
47
69
  acts_as_tree :order => :sort_order, # <- LOOK IT IS A SYMBOL OMG
48
70
  :parent_column_name => "mother_id",
49
71
  :dependent => :destroy
@@ -69,6 +91,6 @@ end
69
91
  module Namespace
70
92
  class Type < ActiveRecord::Base
71
93
  acts_as_tree :dependent => :destroy
72
- attr_accessible :name
94
+ attr_accessible :name if _ct.use_attr_accessible?
73
95
  end
74
96
  end
@@ -0,0 +1,421 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "Tag (without fixtures)" do
4
+
5
+ let (:tag_class) { described_class }
6
+ let (:tag_hierarchy_class) { described_class.hierarchy_class }
7
+
8
+ context 'class setup' do
9
+
10
+ it 'has correct accessible_attributes' do
11
+ if tag_class._ct.use_attr_accessible?
12
+ tag_class.accessible_attributes.to_a.should =~ %w(parent name)
13
+ end
14
+ end
15
+
16
+ it 'should build hierarchy classname correctly' do
17
+ tag_class.hierarchy_class.should == tag_hierarchy_class
18
+ tag_class._ct.hierarchy_class_name.should == tag_hierarchy_class.to_s
19
+ tag_class._ct.short_hierarchy_class_name.should == tag_hierarchy_class.to_s
20
+ end
21
+
22
+ it 'should have a correct parent column name' do
23
+ expected_parent_column_name = tag_class == UUIDTag ? "parent_uuid" : "parent_id"
24
+ tag_class._ct.parent_column_name.should == expected_parent_column_name
25
+ end
26
+ end
27
+
28
+ describe "from empty db" do
29
+ def nuke_db
30
+ tag_hierarchy_class.delete_all
31
+ tag_class.delete_all
32
+ end
33
+
34
+ before :each do
35
+ nuke_db
36
+ end
37
+
38
+ context "with no tags" do
39
+ it "should return no entities" do
40
+ tag_class.roots.should be_empty
41
+ tag_class.leaves.should be_empty
42
+ end
43
+ end
44
+
45
+ context "with 1 tag" do
46
+ it "should return the only entity as a root and leaf" do
47
+ a = tag_class.create!(:name => "a")
48
+ tag_class.roots.should == [a]
49
+ tag_class.leaves.should == [a]
50
+ end
51
+ end
52
+
53
+ context "with 2 tags" do
54
+ before :each do
55
+ @root = tag_class.create!(:name => "root")
56
+ @leaf = @root.add_child(tag_class.create!(:name => "leaf"))
57
+ end
58
+ it "should return a simple root and leaf" do
59
+ tag_class.roots.should == [@root]
60
+ tag_class.leaves.should == [@leaf]
61
+ end
62
+ it "should return child_ids for root" do
63
+ @root.child_ids.should == [@leaf.id]
64
+ end
65
+ it "should return an empty array for leaves" do
66
+ @leaf.child_ids.should be_empty
67
+ end
68
+ end
69
+
70
+ context "3 tag collection.create db" do
71
+ before :each do
72
+ @root = tag_class.create! :name => "root"
73
+ @mid = @root.children.create! :name => "mid"
74
+ @leaf = @mid.children.create! :name => "leaf"
75
+ DestroyedTag.delete_all
76
+ end
77
+
78
+ it "should create all tags" do
79
+ tag_class.all.to_a.should =~ [@root, @mid, @leaf]
80
+ end
81
+
82
+ it "should return a root and leaf without middle tag" do
83
+ tag_class.roots.should == [@root]
84
+ tag_class.leaves.should == [@leaf]
85
+ end
86
+
87
+ it "should delete leaves" do
88
+ tag_class.leaves.destroy_all
89
+ tag_class.roots.should == [@root] # untouched
90
+ tag_class.leaves.should == [@mid]
91
+ end
92
+
93
+ it "should delete everything if you delete the roots" do
94
+ tag_class.roots.destroy_all
95
+ tag_class.all.should be_empty
96
+ tag_class.roots.should be_empty
97
+ tag_class.leaves.should be_empty
98
+ DestroyedTag.all.map { |t| t.name }.should =~ %w{root mid leaf}
99
+ end
100
+ end
101
+
102
+ context "3 tag explicit_create db" do
103
+ before :each do
104
+ @root = tag_class.create!(:name => "root")
105
+ @mid = @root.add_child(tag_class.create!(:name => "mid"))
106
+ @leaf = @mid.add_child(tag_class.create!(:name => "leaf"))
107
+ end
108
+
109
+ it "should create all tags" do
110
+ tag_class.all.to_a.should =~ [@root, @mid, @leaf]
111
+ end
112
+
113
+ it "should return a root and leaf without middle tag" do
114
+ tag_class.roots.should == [@root]
115
+ tag_class.leaves.should == [@leaf]
116
+ end
117
+
118
+ it "should prevent parental loops from torso" do
119
+ @mid.children << @root
120
+ @root.valid?.should be_false
121
+ @mid.reload.children.should == [@leaf]
122
+ end
123
+
124
+ it "should prevent parental loops from toes" do
125
+ @leaf.children << @root
126
+ @root.valid?.should be_false
127
+ @leaf.reload.children.should be_empty
128
+ end
129
+
130
+ it "should support re-parenting" do
131
+ @root.children << @leaf
132
+ tag_class.leaves.should == [@leaf, @mid]
133
+ end
134
+
135
+ it "cleans up hierarchy references for leaves" do
136
+ @leaf.destroy
137
+ tag_hierarchy_class.where(:ancestor_id => @leaf.id).should be_empty
138
+ tag_hierarchy_class.where(:descendant_id => @leaf.id).should be_empty
139
+ end
140
+
141
+ it "cleans up hierarchy references" do
142
+ @mid.destroy
143
+ tag_hierarchy_class.where(:ancestor_id => @mid.id).should be_empty
144
+ tag_hierarchy_class.where(:descendant_id => @mid.id).should be_empty
145
+ @root.reload.should be_root
146
+ root_hiers = @root.ancestor_hierarchies.to_a
147
+ root_hiers.size.should == 1
148
+ tag_hierarchy_class.where(:ancestor_id => @root.id).should == root_hiers
149
+ tag_hierarchy_class.where(:descendant_id => @root.id).should == root_hiers
150
+ end
151
+
152
+ it "should have different hash codes for each hierarchy model" do
153
+ hashes = tag_hierarchy_class.all.map(&:hash)
154
+ hashes.should =~ hashes.uniq
155
+ end
156
+
157
+ it "should return the same hash code for equal hierarchy models" do
158
+ tag_hierarchy_class.first.hash.should == tag_hierarchy_class.first.hash
159
+ end
160
+ end
161
+
162
+ it "performs as the readme says it does" do
163
+ grandparent = tag_class.create(:name => 'Grandparent')
164
+ parent = grandparent.children.create(:name => 'Parent')
165
+ child1 = tag_class.create(:name => 'First Child', :parent => parent)
166
+ child2 = tag_class.new(:name => 'Second Child')
167
+ parent.children << child2
168
+ child3 = tag_class.new(:name => 'Third Child')
169
+ parent.add_child child3
170
+ grandparent.self_and_descendants.collect(&:name).should ==
171
+ ["Grandparent", "Parent", "First Child", "Second Child", "Third Child"]
172
+ child1.ancestry_path.should ==
173
+ ["Grandparent", "Parent", "First Child"]
174
+ child3.ancestry_path.should ==
175
+ ["Grandparent", "Parent", "Third Child"]
176
+ d = tag_class.find_or_create_by_path %w(a b c d)
177
+ h = tag_class.find_or_create_by_path %w(e f g h)
178
+ e = h.root
179
+ d.add_child(e) # "d.children << e" would work too, of course
180
+ h.ancestry_path.should == %w(a b c d e f g h)
181
+ end
182
+
183
+ it "roots sort alphabetically" do
184
+ expected = ("a".."z").to_a
185
+ expected.shuffle.each { |ea| tag_class.create!(:name => ea) }
186
+ tag_class.roots.collect { |ea| ea.name }.should == expected
187
+ end
188
+
189
+ context "with simple tree" do
190
+ before :each do
191
+ tag_class.find_or_create_by_path %w(a1 b1 c1a)
192
+ tag_class.find_or_create_by_path %w(a1 b1 c1b)
193
+ tag_class.find_or_create_by_path %w(a2 b2)
194
+ tag_class.find_or_create_by_path %w(a3)
195
+
196
+ @a1, @a2, @a3, @b1, @b2, @c1a, @c1b = tag_class.where(:name => %w(a1 a2 a3 b1 b2 c1a c1b)).reorder(:name).to_a
197
+ @expected_roots = [@a1, @a2, @a3]
198
+ @expected_leaves = [@c1a, @c1b, @b2, @a3]
199
+ end
200
+ it 'should find global roots' do
201
+ tag_class.roots.to_a.should =~ @expected_roots
202
+ end
203
+ it 'should return root? for roots' do
204
+ @expected_roots.each { |ea| ea.should be_root }
205
+ end
206
+ it 'should not return root? for non-roots' do
207
+ [@b1, @b2, @c1a, @c1b].each { |ea| ea.should_not be_root }
208
+ end
209
+ it 'should return the correct root' do
210
+ {@a1 => @a1, @a2 => @a2, @a3 => @a3,
211
+ @b1 => @a1, @b2 => @a2, @c1a => @a1, @c1b => @a1}.each do |node, root|
212
+ node.root.should == root
213
+ end
214
+ end
215
+ it 'should assemble global leaves' do
216
+ tag_class.leaves.to_a.should =~ @expected_leaves
217
+ end
218
+ it 'should assemble instance leaves' do
219
+ {@a1 => [@c1a, @c1b], @b1 => [@c1a, @c1b], @a2 => [@b2]}.each do |node, leaves|
220
+ node.leaves.to_a.should == leaves
221
+ end
222
+ @expected_leaves.each { |ea| ea.leaves.to_a.should == [ea] }
223
+ end
224
+ it 'should return leaf? for leaves' do
225
+ @expected_leaves.each { |ea| ea.should be_leaf }
226
+ end
227
+ end
228
+
229
+ context "paths" do
230
+ before :each do
231
+ @child = tag_class.find_or_create_by_path(%w(grandparent parent child))
232
+ @child.title = "Kid"
233
+ @parent = @child.parent
234
+ @parent.title = "Mom"
235
+ @grandparent = @parent.parent
236
+ @grandparent.title = "Nonnie"
237
+ [@child, @parent, @grandparent].each { |ea| ea.save! }
238
+ end
239
+
240
+ it "should build ancestry path" do
241
+ @child.ancestry_path.should == %w{grandparent parent child}
242
+ @child.ancestry_path(:name).should == %w{grandparent parent child}
243
+ @child.ancestry_path(:title).should == %w{Nonnie Mom Kid}
244
+ end
245
+
246
+ it "should find by path" do
247
+ # class method:
248
+ tag_class.find_by_path(%w{grandparent parent child}).should == @child
249
+ # instance method:
250
+ @parent.find_by_path(%w{child}).should == @child
251
+ @grandparent.find_by_path(%w{parent child}).should == @child
252
+ @parent.find_by_path(%w{child larvae}).should be_nil
253
+ end
254
+
255
+ it "finds correctly rooted paths" do
256
+ decoy = tag_class.find_or_create_by_path %w(a b c d)
257
+ b_d = tag_class.find_or_create_by_path %w(b c d)
258
+ tag_class.find_by_path(%w(b c d)).should == b_d
259
+ tag_class.find_by_path(%w(c d)).should be_nil
260
+ end
261
+
262
+ it "find_by_path for 1 node" do
263
+ b = tag_class.find_or_create_by_path %w(a b)
264
+ b2 = b.root.find_by_path(%w(b))
265
+ b2.should == b
266
+ end
267
+
268
+ it "find_by_path for 2 nodes" do
269
+ c = tag_class.find_or_create_by_path %w(a b c)
270
+ c.root.find_by_path(%w(b c)).should == c
271
+ c.root.find_by_path(%w(a c)).should be_nil
272
+ c.root.find_by_path(%w(c)).should be_nil
273
+ end
274
+
275
+ it "find_by_path for 3 nodes" do
276
+ d = tag_class.find_or_create_by_path %w(a b c d)
277
+ d.root.find_by_path(%w(b c d)).should == d
278
+ tag_class.find_by_path(%w(a b c d)).should == d
279
+ tag_class.find_by_path(%w(d)).should be_nil
280
+ end
281
+
282
+ it "should return nil for missing nodes" do
283
+ tag_class.find_by_path(%w{missing}).should be_nil
284
+ tag_class.find_by_path(%w{grandparent missing}).should be_nil
285
+ tag_class.find_by_path(%w{grandparent parent missing}).should be_nil
286
+ tag_class.find_by_path(%w{grandparent parent missing child}).should be_nil
287
+ end
288
+
289
+ it "should find or create by path" do
290
+ # class method:
291
+ grandparent = tag_class.find_or_create_by_path(%w{grandparent})
292
+ grandparent.should == @grandparent
293
+ child = tag_class.find_or_create_by_path(%w{grandparent parent child})
294
+ child.should == @child
295
+ tag_class.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
296
+ a = tag_class.find_or_create_by_path(%w{a})
297
+ a.ancestry_path.should == %w{a}
298
+ # instance method:
299
+ a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
300
+ end
301
+ end
302
+
303
+ context "hash_tree" do
304
+
305
+ before :each do
306
+ @b = tag_class.find_or_create_by_path %w(a b)
307
+ @a = @b.parent
308
+ @b2 = tag_class.find_or_create_by_path %w(a b2)
309
+ @d1 = @b.find_or_create_by_path %w(c1 d1)
310
+ @c1 = @d1.parent
311
+ @d2 = @b.find_or_create_by_path %w(c2 d2)
312
+ @c2 = @d2.parent
313
+ @full_tree = {@a => {@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}, @b2 => {}}}
314
+ end
315
+
316
+ context "#hash_tree" do
317
+ it "returns {} for depth 0" do
318
+ tag_class.hash_tree(:limit_depth => 0).should == {}
319
+ end
320
+ it "limit_depth 1" do
321
+ tag_class.hash_tree(:limit_depth => 1).should == {@a => {}}
322
+ end
323
+ it "limit_depth 2" do
324
+ tag_class.hash_tree(:limit_depth => 2).should == {@a => {@b => {}, @b2 => {}}}
325
+ end
326
+ it "limit_depth 3" do
327
+ tag_class.hash_tree(:limit_depth => 3).should == {@a => {@b => {@c1 => {}, @c2 => {}}, @b2 => {}}}
328
+ end
329
+ it "limit_depth 4" do
330
+ tag_class.hash_tree(:limit_depth => 4).should == @full_tree
331
+ end
332
+ it "no limit holdum" do
333
+ tag_class.hash_tree.should == @full_tree
334
+ end
335
+ end
336
+
337
+ def assert_no_dupes(scope)
338
+ # the named scope is complicated enough that an incorrect join could result in unnecessarily
339
+ # duplicated rows:
340
+ a = scope.collect { |ea| ea.id }
341
+ a.should == a.uniq
342
+ end
343
+
344
+ context "#hash_tree_scope" do
345
+ it "no dupes for any depth" do
346
+ (0..5).each do |ea|
347
+ assert_no_dupes(tag_class.hash_tree_scope(ea))
348
+ end
349
+ end
350
+ it "no limit holdum" do
351
+ assert_no_dupes(tag_class.hash_tree_scope)
352
+ end
353
+ end
354
+
355
+ context ".hash_tree_scope" do
356
+ it "no dupes for any depth" do
357
+ (0..5).each do |ea|
358
+ assert_no_dupes(@a.hash_tree_scope(ea))
359
+ end
360
+ end
361
+ it "no limit holdum" do
362
+ assert_no_dupes(@a.hash_tree_scope)
363
+ end
364
+ end
365
+
366
+ context ".hash_tree" do
367
+ before :each do
368
+ end
369
+ it "returns {} for depth 0" do
370
+ @b.hash_tree(:limit_depth => 0).should == {}
371
+ end
372
+ it "limit_depth 1" do
373
+ @b.hash_tree(:limit_depth => 1).should == {@b => {}}
374
+ end
375
+ it "limit_depth 2" do
376
+ @b.hash_tree(:limit_depth => 2).should == {@b => {@c1 => {}, @c2 => {}}}
377
+ end
378
+ it "limit_depth 3" do
379
+ @b.hash_tree(:limit_depth => 3).should == {@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}}
380
+ end
381
+ it "no limit holdum from subsubroot" do
382
+ @c1.hash_tree.should == {@c1 => {@d1 => {}}}
383
+ end
384
+ it "no limit holdum from subroot" do
385
+ @b.hash_tree.should == {@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}}
386
+ end
387
+ it "no limit holdum from root" do
388
+ @a.hash_tree.should == @full_tree
389
+ end
390
+ end
391
+ end
392
+
393
+ describe 'DOT rendering' do
394
+ it 'should render for an empty scope' do
395
+ tag_class.to_dot_digraph(tag_class.where("0=1")).should == "digraph G {\n}\n"
396
+ end
397
+ it 'should render for an empty scope' do
398
+ tag_class.find_or_create_by_path(%w(a b1 c1))
399
+ tag_class.find_or_create_by_path(%w(a b2 c2))
400
+ tag_class.find_or_create_by_path(%w(a b2 c3))
401
+ a, b1, b2, c1, c2, c3 = %w(a b1 b2 c1 c2 c3).map { |ea| tag_class.where(:name => ea).first.id }
402
+ dot = tag_class.roots.first.to_dot_digraph
403
+ dot.should == <<-DOT
404
+ digraph G {
405
+ #{a} [label="a"]
406
+ #{a} -> #{b1}
407
+ #{b1} [label="b1"]
408
+ #{a} -> #{b2}
409
+ #{b2} [label="b2"]
410
+ #{b1} -> #{c1}
411
+ #{c1} [label="c1"]
412
+ #{b2} -> #{c2}
413
+ #{c2} [label="c2"]
414
+ #{b2} -> #{c3}
415
+ #{c3} [label="c3"]
416
+ }
417
+ DOT
418
+ end
419
+ end
420
+ end
421
+ end