closure_tree 6.6.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -6
  3. data/Appraisals +59 -1
  4. data/CHANGELOG.md +21 -0
  5. data/Gemfile +0 -12
  6. data/README.md +40 -3
  7. data/closure_tree.gemspec +10 -7
  8. data/lib/closure_tree/finders.rb +15 -5
  9. data/lib/closure_tree/has_closure_tree.rb +2 -0
  10. data/lib/closure_tree/has_closure_tree_root.rb +2 -6
  11. data/lib/closure_tree/hash_tree_support.rb +1 -1
  12. data/lib/closure_tree/hierarchy_maintenance.rb +3 -3
  13. data/lib/closure_tree/model.rb +31 -1
  14. data/lib/closure_tree/numeric_deterministic_ordering.rb +11 -2
  15. data/lib/closure_tree/numeric_order_support.rb +3 -0
  16. data/lib/closure_tree/support.rb +7 -3
  17. data/lib/closure_tree/support_attributes.rb +9 -0
  18. data/lib/closure_tree/support_flags.rb +1 -4
  19. data/lib/closure_tree/version.rb +1 -1
  20. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  21. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  22. metadata +28 -76
  23. data/gemfiles/activerecord_4.2.gemfile +0 -19
  24. data/gemfiles/activerecord_5.0.gemfile +0 -19
  25. data/gemfiles/activerecord_5.1.gemfile +0 -19
  26. data/gemfiles/activerecord_edge.gemfile +0 -20
  27. data/img/example.png +0 -0
  28. data/img/preorder.png +0 -0
  29. data/spec/cache_invalidation_spec.rb +0 -39
  30. data/spec/cuisine_type_spec.rb +0 -38
  31. data/spec/db/database.yml +0 -21
  32. data/spec/db/models.rb +0 -128
  33. data/spec/db/schema.rb +0 -166
  34. data/spec/fixtures/tags.yml +0 -98
  35. data/spec/generators/migration_generator_spec.rb +0 -48
  36. data/spec/has_closure_tree_root_spec.rb +0 -154
  37. data/spec/hierarchy_maintenance_spec.rb +0 -16
  38. data/spec/label_spec.rb +0 -554
  39. data/spec/matcher_spec.rb +0 -34
  40. data/spec/metal_spec.rb +0 -55
  41. data/spec/model_spec.rb +0 -9
  42. data/spec/namespace_type_spec.rb +0 -13
  43. data/spec/parallel_spec.rb +0 -159
  44. data/spec/pool_spec.rb +0 -27
  45. data/spec/spec_helper.rb +0 -24
  46. data/spec/support/database.rb +0 -52
  47. data/spec/support/database_cleaner.rb +0 -14
  48. data/spec/support/exceed_query_limit.rb +0 -18
  49. data/spec/support/hash_monkey_patch.rb +0 -13
  50. data/spec/support/query_counter.rb +0 -18
  51. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  52. data/spec/support_spec.rb +0 -14
  53. data/spec/tag_examples.rb +0 -665
  54. data/spec/tag_spec.rb +0 -6
  55. data/spec/user_spec.rb +0 -174
  56. data/spec/uuid_tag_spec.rb +0 -6
@@ -1,10 +0,0 @@
1
- if sqlite?
2
- RSpec.configure do |config|
3
- config.before(:suite) do
4
- ENV['FLOCK_DIR'] = Dir.mktmpdir
5
- end
6
- config.after(:suite) do
7
- FileUtils.remove_entry_secure ENV['FLOCK_DIR']
8
- end
9
- end
10
- end
@@ -1,14 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ClosureTree::Support do
4
- let(:sut) { Tag._ct }
5
- it 'passes through table names without prefix and suffix' do
6
- expected = 'some_random_table_name'
7
- expect(sut.remove_prefix_and_suffix(expected)).to eq(expected)
8
- end
9
- it 'extracts through table name with prefix and suffix' do
10
- expected = 'some_random_table_name'
11
- tn = ActiveRecord::Base.table_name_prefix + expected + ActiveRecord::Base.table_name_suffix
12
- expect(sut.remove_prefix_and_suffix(tn)).to eq(expected)
13
- end
14
- end
@@ -1,665 +0,0 @@
1
- require 'spec_helper'
2
-
3
- shared_examples_for Tag 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
- expect(tag_class.accessible_attributes.to_a).to match_array(%w(parent name title))
13
- end
14
- end
15
-
16
- it 'should build hierarchy classname correctly' do
17
- expect(tag_class.hierarchy_class).to eq(tag_hierarchy_class)
18
- expect(tag_class._ct.hierarchy_class_name).to eq(tag_hierarchy_class.to_s)
19
- expect(tag_class._ct.short_hierarchy_class_name).to eq(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
- expect(tag_class._ct.parent_column_name).to eq(expected_parent_column_name)
25
- end
26
- end
27
-
28
- describe 'from empty db' do
29
-
30
- context 'with no tags' do
31
- it 'should return no entities' do
32
- expect(tag_class.roots).to be_empty
33
- expect(tag_class.leaves).to be_empty
34
- end
35
-
36
- it '#find_or_create_by_path with strings' do
37
- a = tag_class.create!(name: 'a')
38
- expect(a.find_or_create_by_path(%w{b c}).ancestry_path).to eq(%w{a b c})
39
- end
40
-
41
- it '#find_or_create_by_path with hashes' do
42
- a = tag_class.create!(name: 'a', title: 'A')
43
- subject = a.find_or_create_by_path([
44
- {name: 'b', title: 'B'},
45
- {name: 'c', title: 'C'}
46
- ])
47
- expect(subject.ancestry_path).to eq(%w{a b c})
48
- expect(subject.self_and_ancestors.map(&:title)).to eq(%w{C B A})
49
- end
50
- end
51
-
52
- context 'with 1 tag' do
53
- before do
54
- @tag = tag_class.create!(name: 'tag')
55
- end
56
-
57
- it 'should be a leaf' do
58
- expect(@tag.leaf?).to be_truthy
59
- end
60
-
61
- it 'should be a root' do
62
- expect(@tag.root?).to be_truthy
63
- end
64
-
65
- it 'has no parent' do
66
- expect(@tag.parent).to be_nil
67
- end
68
-
69
- it 'should return the only entity as a root and leaf' do
70
- expect(tag_class.all).to eq([@tag])
71
- expect(tag_class.roots).to eq([@tag])
72
- expect(tag_class.leaves).to eq([@tag])
73
- end
74
-
75
- context 'with child' do
76
- before do
77
- @child = tag_class.create!(name: 'tag 2')
78
- end
79
-
80
- def assert_roots_and_leaves
81
- expect(@tag.root?).to be_truthy
82
- expect(@tag.leaf?).to be_falsey
83
-
84
- expect(@child.root?).to be_falsey
85
- expect(@child.leaf?).to be_truthy
86
- end
87
-
88
- def assert_parent_and_children
89
- expect(@child.reload.parent).to eq(@tag)
90
- expect(@tag.reload.children.to_a).to eq([@child])
91
- end
92
-
93
- it 'adds children through add_child' do
94
- @tag.add_child @child
95
- assert_roots_and_leaves
96
- assert_parent_and_children
97
- end
98
-
99
- it 'adds children through collection' do
100
- @tag.children << @child
101
- assert_roots_and_leaves
102
- assert_parent_and_children
103
- end
104
- end
105
- end
106
-
107
- context 'with 2 tags' do
108
- before :each do
109
- @root = tag_class.create!(name: 'root')
110
- @leaf = @root.add_child(tag_class.create!(name: 'leaf'))
111
- end
112
- it 'should return a simple root and leaf' do
113
- expect(tag_class.roots).to eq([@root])
114
- expect(tag_class.leaves).to eq([@leaf])
115
- end
116
- it 'should return child_ids for root' do
117
- expect(@root.child_ids).to eq([@leaf.id])
118
- end
119
-
120
- it 'should return an empty array for leaves' do
121
- expect(@leaf.child_ids).to be_empty
122
- end
123
- end
124
-
125
- context '3 tag collection.create db' do
126
- before :each do
127
- @root = tag_class.create! name: 'root'
128
- @mid = @root.children.create! name: 'mid'
129
- @leaf = @mid.children.create! name: 'leaf'
130
- DestroyedTag.delete_all
131
- end
132
-
133
- it 'should create all tags' do
134
- expect(tag_class.all.to_a).to match_array([@root, @mid, @leaf])
135
- end
136
-
137
- it 'should return a root and leaf without middle tag' do
138
- expect(tag_class.roots).to eq([@root])
139
- expect(tag_class.leaves).to eq([@leaf])
140
- end
141
-
142
- it 'should delete leaves' do
143
- tag_class.leaves.destroy_all
144
- expect(tag_class.roots).to eq([@root]) # untouched
145
- expect(tag_class.leaves).to eq([@mid])
146
- end
147
-
148
- it 'should delete everything if you delete the roots' do
149
- tag_class.roots.destroy_all
150
- expect(tag_class.all).to be_empty
151
- expect(tag_class.roots).to be_empty
152
- expect(tag_class.leaves).to be_empty
153
- expect(DestroyedTag.all.map { |t| t.name }).to match_array(%w{root mid leaf})
154
- end
155
-
156
- it 'fix self_and_ancestors properly on reparenting' do
157
- t = tag_class.create! name: 'moar leaf'
158
- expect(t.self_and_ancestors.to_a).to eq([t])
159
- @mid.children << t
160
- expect(t.self_and_ancestors.to_a).to eq([t, @mid, @root])
161
- end
162
-
163
- it 'prevents ancestor loops' do
164
- @leaf.add_child @root
165
- expect(@root).not_to be_valid
166
- expect(@root.reload.descendants).to include(@leaf)
167
- end
168
-
169
- it 'moves non-leaves' do
170
- new_root = tag_class.create! name: 'new_root'
171
- new_root.children << @mid
172
- expect(@root.reload.descendants).to be_empty
173
- expect(new_root.descendants).to eq([@mid, @leaf])
174
- expect(@leaf.reload.ancestry_path).to eq(%w{new_root mid leaf})
175
- end
176
-
177
- it 'moves leaves' do
178
- new_root = tag_class.create! name: 'new_root'
179
- new_root.children << @leaf
180
- expect(new_root.descendants).to eq([@leaf])
181
- expect(@root.reload.descendants).to eq([@mid])
182
- expect(@leaf.reload.ancestry_path).to eq(%w{new_root leaf})
183
- end
184
- end
185
-
186
- context '3 tag explicit_create db' do
187
- before :each do
188
- @root = tag_class.create!(name: 'root')
189
- @mid = @root.add_child(tag_class.create!(name: 'mid'))
190
- @leaf = @mid.add_child(tag_class.create!(name: 'leaf'))
191
- end
192
-
193
- it 'should create all tags' do
194
- expect(tag_class.all.to_a).to match_array([@root, @mid, @leaf])
195
- end
196
-
197
- it 'should return a root and leaf without middle tag' do
198
- expect(tag_class.roots).to eq([@root])
199
- expect(tag_class.leaves).to eq([@leaf])
200
- end
201
-
202
- it 'should prevent parental loops from torso' do
203
- @mid.children << @root
204
- expect(@root.valid?).to be_falsey
205
- expect(@mid.reload.children).to eq([@leaf])
206
- end
207
-
208
- it 'should prevent parental loops from toes' do
209
- @leaf.children << @root
210
- expect(@root.valid?).to be_falsey
211
- expect(@leaf.reload.children).to be_empty
212
- end
213
-
214
- it 'should support re-parenting' do
215
- @root.children << @leaf
216
- expect(tag_class.leaves).to eq([@leaf, @mid])
217
- end
218
-
219
- it 'cleans up hierarchy references for leaves' do
220
- @leaf.destroy
221
- expect(tag_hierarchy_class.where(ancestor_id: @leaf.id)).to be_empty
222
- expect(tag_hierarchy_class.where(descendant_id: @leaf.id)).to be_empty
223
- end
224
-
225
- it 'cleans up hierarchy references' do
226
- @mid.destroy
227
- expect(tag_hierarchy_class.where(ancestor_id: @mid.id)).to be_empty
228
- expect(tag_hierarchy_class.where(descendant_id: @mid.id)).to be_empty
229
- expect(@root.reload).to be_root
230
- root_hiers = @root.ancestor_hierarchies.to_a
231
- expect(root_hiers.size).to eq(1)
232
- expect(tag_hierarchy_class.where(ancestor_id: @root.id)).to eq(root_hiers)
233
- expect(tag_hierarchy_class.where(descendant_id: @root.id)).to eq(root_hiers)
234
- end
235
-
236
- it 'should have different hash codes for each hierarchy model' do
237
- hashes = tag_hierarchy_class.all.map(&:hash)
238
- expect(hashes).to match_array(hashes.uniq)
239
- end
240
-
241
- it 'should return the same hash code for equal hierarchy models' do
242
- expect(tag_hierarchy_class.first.hash).to eq(tag_hierarchy_class.first.hash)
243
- end
244
- end
245
-
246
- it 'performs as the readme says it does' do
247
- grandparent = tag_class.create(name: 'Grandparent')
248
- parent = grandparent.children.create(name: 'Parent')
249
- child1 = tag_class.create(name: 'First Child', parent: parent)
250
- child2 = tag_class.new(name: 'Second Child')
251
- parent.children << child2
252
- child3 = tag_class.new(name: 'Third Child')
253
- parent.add_child child3
254
- expect(grandparent.self_and_descendants.collect(&:name)).to eq(
255
- ['Grandparent', 'Parent', 'First Child', 'Second Child', 'Third Child']
256
- )
257
- expect(child1.ancestry_path).to eq(
258
- ['Grandparent', 'Parent', 'First Child']
259
- )
260
- expect(child3.ancestry_path).to eq(
261
- ['Grandparent', 'Parent', 'Third Child']
262
- )
263
- d = tag_class.find_or_create_by_path %w(a b c d)
264
- h = tag_class.find_or_create_by_path %w(e f g h)
265
- e = h.root
266
- d.add_child(e) # "d.children << e" would work too, of course
267
- expect(h.ancestry_path).to eq(%w(a b c d e f g h))
268
- end
269
-
270
- it 'roots sort alphabetically' do
271
- expected = ('a'..'z').to_a
272
- expected.shuffle.each { |ea| tag_class.create!(name: ea) }
273
- expect(tag_class.roots.collect { |ea| ea.name }).to eq(expected)
274
- end
275
-
276
- context 'with simple tree' do
277
- before :each do
278
- tag_class.find_or_create_by_path %w(a1 b1 c1a)
279
- tag_class.find_or_create_by_path %w(a1 b1 c1b)
280
- tag_class.find_or_create_by_path %w(a1 b1 c1c)
281
- tag_class.find_or_create_by_path %w(a1 b1b)
282
- tag_class.find_or_create_by_path %w(a2 b2)
283
- tag_class.find_or_create_by_path %w(a3)
284
-
285
- @a1, @a2, @a3, @b1, @b1b, @b2, @c1a, @c1b, @c1c =
286
- tag_class.all.sort_by(&:name)
287
- @expected_roots = [@a1, @a2, @a3]
288
- @expected_leaves = [@c1a, @c1b, @c1c, @b1b, @b2, @a3]
289
- @expected_siblings = [[@a1, @a2, @a3], [@b1, @b1b], [@c1a, @c1b, @c1c]]
290
- @expected_only_children = tag_class.all - @expected_siblings.flatten
291
- end
292
-
293
- it 'should find global roots' do
294
- expect(tag_class.roots.to_a).to match_array(@expected_roots)
295
- end
296
- it 'should return root? for roots' do
297
- @expected_roots.each { |ea| expect(ea).to be_root }
298
- end
299
- it 'should not return root? for non-roots' do
300
- [@b1, @b2, @c1a, @c1b].each { |ea| expect(ea).not_to be_root }
301
- end
302
- it 'should return the correct root' do
303
- {@a1 => @a1, @a2 => @a2, @a3 => @a3,
304
- @b1 => @a1, @b2 => @a2, @c1a => @a1, @c1b => @a1}.each do |node, root|
305
- expect(node.root).to eq(root)
306
- end
307
- end
308
- it 'should assemble global leaves' do
309
- expect(tag_class.leaves.to_a).to match_array(@expected_leaves)
310
- end
311
- it 'assembles siblings properly' do
312
- @expected_siblings.each do |siblings|
313
- siblings.each do |ea|
314
- expect(ea.self_and_siblings.to_a).to match_array(siblings)
315
- expect(ea.siblings.to_a).to match_array(siblings - [ea])
316
- end
317
- end
318
- @expected_only_children.each do |ea|
319
- expect(ea.siblings).to eq([])
320
- end
321
- end
322
- it 'assembles before_siblings' do
323
- @expected_siblings.each do |siblings|
324
- (siblings.size - 1).times do |i|
325
- target = siblings[i]
326
- expected_before = siblings.first(i)
327
- expect(target.siblings_before.to_a).to eq(expected_before)
328
- end
329
- end
330
- end
331
- it 'assembles after_siblings' do
332
- @expected_siblings.each do |siblings|
333
- (siblings.size - 1).times do |i|
334
- target = siblings[i]
335
- expected_after = siblings.last(siblings.size - 1 - i)
336
- expect(target.siblings_after.to_a).to eq(expected_after)
337
- end
338
- end
339
- end
340
- it 'should assemble instance leaves' do
341
- {@a1 => [@b1b, @c1a, @c1b, @c1c], @b1 => [@c1a, @c1b, @c1c], @a2 => [@b2]}.each do |node, leaves|
342
- expect(node.leaves.to_a).to eq(leaves)
343
- end
344
- @expected_leaves.each { |ea| expect(ea.leaves.to_a).to eq([ea]) }
345
- end
346
- it 'should return leaf? for leaves' do
347
- @expected_leaves.each { |ea| expect(ea).to be_leaf }
348
- end
349
-
350
- it 'can move roots' do
351
- @c1a.children << @a2
352
- @b2.reload.children << @a3
353
- expect(@a3.reload.ancestry_path).to eq(%w(a1 b1 c1a a2 b2 a3))
354
- end
355
-
356
- it 'cascade-deletes from roots' do
357
- victim_names = @a1.self_and_descendants.map(&:name)
358
- survivor_names = tag_class.all.map(&:name) - victim_names
359
- @a1.destroy
360
- expect(tag_class.all.map(&:name)).to eq(survivor_names)
361
- end
362
- end
363
-
364
- context 'with_ancestor' do
365
- it 'works with no rows' do
366
- expect(tag_class.with_ancestor.to_a).to be_empty
367
- end
368
- it 'finds only children' do
369
- c = tag_class.find_or_create_by_path %w(A B C)
370
- a, b = c.parent.parent, c.parent
371
- spurious_tags = tag_class.find_or_create_by_path %w(D E)
372
- expect(tag_class.with_ancestor(a).to_a).to eq([b, c])
373
- end
374
- it 'limits subsequent where clauses' do
375
- a1c = tag_class.find_or_create_by_path %w(A1 B C)
376
- a2c = tag_class.find_or_create_by_path %w(A2 B C)
377
- # different paths!
378
- expect(a1c).not_to eq(a2c)
379
- expect(tag_class.where(:name => 'C').to_a).to match_array([a1c, a2c])
380
- expect(tag_class.with_ancestor(a1c.parent.parent).where(:name => 'C').to_a).to eq([a1c])
381
- end
382
- end
383
-
384
- context 'paths' do
385
- context 'with grandchild' do
386
- before do
387
- @child = tag_class.find_or_create_by_path([
388
- {name: 'grandparent', title: 'Nonnie'},
389
- {name: 'parent', title: 'Mom'},
390
- {name: 'child', title: 'Kid'}])
391
- @parent = @child.parent
392
- @grandparent = @parent.parent
393
- end
394
-
395
- it 'should build ancestry path' do
396
- expect(@child.ancestry_path).to eq(%w{grandparent parent child})
397
- expect(@child.ancestry_path(:name)).to eq(%w{grandparent parent child})
398
- expect(@child.ancestry_path(:title)).to eq(%w{Nonnie Mom Kid})
399
- end
400
-
401
- it 'assembles ancestors' do
402
- expect(@child.ancestors).to eq([@parent, @grandparent])
403
- expect(@child.self_and_ancestors).to eq([@child, @parent, @grandparent])
404
- end
405
-
406
- it 'should find by path' do
407
- # class method:
408
- expect(tag_class.find_by_path(%w{grandparent parent child})).to eq(@child)
409
- # instance method:
410
- expect(@parent.find_by_path(%w{child})).to eq(@child)
411
- expect(@grandparent.find_by_path(%w{parent child})).to eq(@child)
412
- expect(@parent.find_by_path(%w{child larvae})).to be_nil
413
- end
414
-
415
- it 'should respect attribute hashes with both selection and creation' do
416
- expected_title = 'something else'
417
- attrs = {title: expected_title}
418
- existing_title = @grandparent.title
419
- new_grandparent = tag_class.find_or_create_by_path(%w{grandparent}, attrs)
420
- expect(new_grandparent).not_to eq(@grandparent)
421
- expect(new_grandparent.title).to eq(expected_title)
422
- expect(@grandparent.reload.title).to eq(existing_title)
423
- end
424
-
425
- it 'should create a hierarchy with a given attribute' do
426
- expected_title = 'unicorn rainbows'
427
- attrs = {title: expected_title}
428
- child = tag_class.find_or_create_by_path(%w{grandparent parent child}, attrs)
429
- expect(child).not_to eq(@child)
430
- [child, child.parent, child.parent.parent].each do |ea|
431
- expect(ea.title).to eq(expected_title)
432
- end
433
- end
434
- end
435
-
436
- it 'finds correctly rooted paths' do
437
- decoy = tag_class.find_or_create_by_path %w(a b c d)
438
- b_d = tag_class.find_or_create_by_path %w(b c d)
439
- expect(tag_class.find_by_path(%w(b c d))).to eq(b_d)
440
- expect(tag_class.find_by_path(%w(c d))).to be_nil
441
- end
442
-
443
- it 'find_by_path for 1 node' do
444
- b = tag_class.find_or_create_by_path %w(a b)
445
- b2 = b.root.find_by_path(%w(b))
446
- expect(b2).to eq(b)
447
- end
448
-
449
- it 'find_by_path for 2 nodes' do
450
- path = %w(a b c)
451
- c = tag_class.find_or_create_by_path path
452
- permutations = path.permutation.to_a
453
- correct = %w(b c)
454
- expect(c.root.find_by_path(correct)).to eq(c)
455
- (permutations - correct).each do |bad_path|
456
- expect(c.root.find_by_path(bad_path)).to be_nil
457
- end
458
- end
459
-
460
- it 'find_by_path for 3 nodes' do
461
- d = tag_class.find_or_create_by_path %w(a b c d)
462
- expect(d.root.find_by_path(%w(b c d))).to eq(d)
463
- expect(tag_class.find_by_path(%w(a b c d))).to eq(d)
464
- expect(tag_class.find_by_path(%w(d))).to be_nil
465
- end
466
-
467
- it 'should return nil for missing nodes' do
468
- expect(tag_class.find_by_path(%w{missing})).to be_nil
469
- expect(tag_class.find_by_path(%w{grandparent missing})).to be_nil
470
- expect(tag_class.find_by_path(%w{grandparent parent missing})).to be_nil
471
- expect(tag_class.find_by_path(%w{grandparent parent missing child})).to be_nil
472
- end
473
-
474
- describe '.find_or_create_by_path' do
475
- it 'uses existing records' do
476
- grandparent = tag_class.find_or_create_by_path(%w{grandparent})
477
- expect(grandparent).to eq(grandparent)
478
- child = tag_class.find_or_create_by_path(%w{grandparent parent child})
479
- expect(child).to eq(child)
480
- end
481
-
482
- it 'creates 2-deep trees with strings' do
483
- subject = tag_class.find_or_create_by_path(%w{events anniversary})
484
- expect(subject.ancestry_path).to eq(%w{events anniversary})
485
- end
486
-
487
- it 'creates 2-deep trees with hashes' do
488
- subject = tag_class.find_or_create_by_path([
489
- {name: 'test1', title: 'TEST1'},
490
- {name: 'test2', title: 'TEST2'}
491
- ])
492
- expect(subject.ancestry_path).to eq(%w{test1 test2})
493
- # `self_and_ancestors` and `ancestors` is ordered parent-first. (!!)
494
- expect(subject.self_and_ancestors.map(&:title)).to eq(%w{TEST2 TEST1})
495
- end
496
-
497
- end
498
- end
499
-
500
- context 'hash_tree' do
501
- before :each do
502
- @d1 = tag_class.find_or_create_by_path %w(a b c1 d1)
503
- @c1 = @d1.parent
504
- @b = @c1.parent
505
- @a = @b.parent
506
- @a2 = tag_class.create(name: 'a2')
507
- @b2 = tag_class.find_or_create_by_path %w(a b2)
508
- @c3 = tag_class.find_or_create_by_path %w(a3 b3 c3)
509
- @b3 = @c3.parent
510
- @a3 = @b3.parent
511
- @tree2 = {
512
- @a => {@b => {}, @b2 => {}}, @a2 => {}, @a3 => {@b3 => {}}
513
- }
514
-
515
- @one_tree = {
516
- @a => {},
517
- @a2 => {},
518
- @a3 => {}
519
- }
520
- @two_tree = {
521
- @a => {
522
- @b => {},
523
- @b2 => {}
524
- },
525
- @a2 => {},
526
- @a3 => {
527
- @b3 => {}
528
- }
529
- }
530
- @three_tree = {
531
- @a => {
532
- @b => {
533
- @c1 => {},
534
- },
535
- @b2 => {}
536
- },
537
- @a2 => {},
538
- @a3 => {
539
- @b3 => {
540
- @c3 => {}
541
- }
542
- }
543
- }
544
- @full_tree = {
545
- @a => {
546
- @b => {
547
- @c1 => {
548
- @d1 => {}
549
- },
550
- },
551
- @b2 => {}
552
- },
553
- @a2 => {},
554
- @a3 => {
555
- @b3 => {
556
- @c3 => {}
557
- }
558
- }
559
- }
560
- #File.open("example.dot", "w") { |f| f.write(tag_class.root.to_dot_digraph) }
561
- end
562
-
563
- context '#hash_tree' do
564
- it 'returns {} for depth 0' do
565
- expect(tag_class.hash_tree(limit_depth: 0)).to eq({})
566
- end
567
- it 'limit_depth 1' do
568
- expect(tag_class.hash_tree(limit_depth: 1)).to eq(@one_tree)
569
- end
570
- it 'limit_depth 2' do
571
- expect(tag_class.hash_tree(limit_depth: 2)).to eq(@two_tree)
572
- end
573
- it 'limit_depth 3' do
574
- expect(tag_class.hash_tree(limit_depth: 3)).to eq(@three_tree)
575
- end
576
- it 'limit_depth 4' do
577
- expect(tag_class.hash_tree(limit_depth: 4)).to eq(@full_tree)
578
- end
579
- it 'no limit' do
580
- expect(tag_class.hash_tree).to eq(@full_tree)
581
- end
582
- end
583
-
584
- context '.hash_tree' do
585
- it 'returns {} for depth 0' do
586
- expect(@b.hash_tree(limit_depth: 0)).to eq({})
587
- end
588
- it 'limit_depth 1' do
589
- expect(@b.hash_tree(limit_depth: 1)).to eq(@two_tree[@a].slice(@b))
590
- end
591
- it 'limit_depth 2' do
592
- expect(@b.hash_tree(limit_depth: 2)).to eq(@three_tree[@a].slice(@b))
593
- end
594
- it 'limit_depth 3' do
595
- expect(@b.hash_tree(limit_depth: 3)).to eq(@full_tree[@a].slice(@b))
596
- end
597
- it 'no limit from subsubroot' do
598
- expect(@c1.hash_tree).to eq(@full_tree[@a][@b].slice(@c1))
599
- end
600
- it 'no limit from subroot' do
601
- expect(@b.hash_tree).to eq(@full_tree[@a].slice(@b))
602
- end
603
- it 'no limit from root' do
604
- expect(@a.hash_tree.merge(@a2.hash_tree)).to eq(@full_tree.slice(@a, @a2))
605
- end
606
- end
607
-
608
- context '.hash_tree from relations' do
609
- it 'limit_depth 2 from chained activerecord association subroots' do
610
- expect(@a.children.hash_tree(limit_depth: 2)).to eq(@three_tree[@a])
611
- end
612
- it 'no limit from chained activerecord association subroots' do
613
- expect(@a.children.hash_tree).to eq(@full_tree[@a])
614
- end
615
- it 'limit_depth 3 from b.parent' do
616
- expect(@b.parent.hash_tree(limit_depth: 3)).to eq(@three_tree.slice(@a))
617
- end
618
- it 'no limit_depth from b.parent' do
619
- expect(@b.parent.hash_tree).to eq(@full_tree.slice(@a))
620
- end
621
- it 'no limit_depth from c.parent' do
622
- expect(@c1.parent.hash_tree).to eq(@full_tree[@a].slice(@b))
623
- end
624
- end
625
- end
626
-
627
- it 'finds_by_path for very deep trees' do
628
- expect(tag_class._ct).to receive(:max_join_tables).at_least(1).and_return(3)
629
- path = (1..20).to_a.map { |ea| ea.to_s }
630
- subject = tag_class.find_or_create_by_path(path)
631
- expect(subject.ancestry_path).to eq(path)
632
- expect(tag_class.find_by_path(path)).to eq(subject)
633
- root = subject.root
634
- expect(root.find_by_path(path[1..-1])).to eq(subject)
635
- end
636
-
637
- describe 'DOT rendering' do
638
- it 'should render for an empty scope' do
639
- expect(tag_class.to_dot_digraph(tag_class.where('0=1'))).to eq("digraph G {\n}\n")
640
- end
641
- it 'should render for an empty scope' do
642
- tag_class.find_or_create_by_path(%w(a b1 c1))
643
- tag_class.find_or_create_by_path(%w(a b2 c2))
644
- tag_class.find_or_create_by_path(%w(a b2 c3))
645
- a, b1, b2, c1, c2, c3 = %w(a b1 b2 c1 c2 c3).map { |ea| tag_class.where(name: ea).first.id }
646
- dot = tag_class.roots.first.to_dot_digraph
647
- expect(dot).to eq <<-DOT
648
- digraph G {
649
- "#{a}" [label="a"]
650
- "#{a}" -> "#{b1}"
651
- "#{b1}" [label="b1"]
652
- "#{a}" -> "#{b2}"
653
- "#{b2}" [label="b2"]
654
- "#{b1}" -> "#{c1}"
655
- "#{c1}" [label="c1"]
656
- "#{b2}" -> "#{c2}"
657
- "#{c2}" [label="c2"]
658
- "#{b2}" -> "#{c3}"
659
- "#{c3}" [label="c3"]
660
- }
661
- DOT
662
- end
663
- end
664
- end
665
- end