closure_tree 6.5.0 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +98 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +1 -1
  5. data/Appraisals +90 -7
  6. data/CHANGELOG.md +100 -42
  7. data/Gemfile +3 -11
  8. data/README.md +68 -24
  9. data/Rakefile +16 -10
  10. data/_config.yml +1 -0
  11. data/bin/appraisal +29 -0
  12. data/bin/rake +29 -0
  13. data/bin/rspec +29 -0
  14. data/closure_tree.gemspec +16 -9
  15. data/lib/closure_tree/finders.rb +32 -9
  16. data/lib/closure_tree/has_closure_tree.rb +4 -0
  17. data/lib/closure_tree/has_closure_tree_root.rb +5 -7
  18. data/lib/closure_tree/hash_tree_support.rb +4 -4
  19. data/lib/closure_tree/hierarchy_maintenance.rb +28 -8
  20. data/lib/closure_tree/model.rb +42 -16
  21. data/lib/closure_tree/numeric_deterministic_ordering.rb +20 -6
  22. data/lib/closure_tree/numeric_order_support.rb +7 -3
  23. data/lib/closure_tree/support.rb +18 -12
  24. data/lib/closure_tree/support_attributes.rb +10 -1
  25. data/lib/closure_tree/support_flags.rb +1 -4
  26. data/lib/closure_tree/version.rb +1 -1
  27. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  28. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  29. metadata +78 -79
  30. data/.travis.yml +0 -29
  31. data/gemfiles/activerecord_4.2.gemfile +0 -19
  32. data/gemfiles/activerecord_5.0.gemfile +0 -19
  33. data/gemfiles/activerecord_5.0_foreigner.gemfile +0 -20
  34. data/gemfiles/activerecord_edge.gemfile +0 -20
  35. data/img/example.png +0 -0
  36. data/img/preorder.png +0 -0
  37. data/spec/cache_invalidation_spec.rb +0 -39
  38. data/spec/cuisine_type_spec.rb +0 -38
  39. data/spec/db/database.yml +0 -21
  40. data/spec/db/models.rb +0 -128
  41. data/spec/db/schema.rb +0 -166
  42. data/spec/fixtures/tags.yml +0 -98
  43. data/spec/generators/migration_generator_spec.rb +0 -48
  44. data/spec/has_closure_tree_root_spec.rb +0 -154
  45. data/spec/hierarchy_maintenance_spec.rb +0 -16
  46. data/spec/label_spec.rb +0 -554
  47. data/spec/matcher_spec.rb +0 -34
  48. data/spec/metal_spec.rb +0 -55
  49. data/spec/model_spec.rb +0 -9
  50. data/spec/namespace_type_spec.rb +0 -13
  51. data/spec/parallel_spec.rb +0 -159
  52. data/spec/spec_helper.rb +0 -24
  53. data/spec/support/database.rb +0 -52
  54. data/spec/support/database_cleaner.rb +0 -14
  55. data/spec/support/exceed_query_limit.rb +0 -18
  56. data/spec/support/hash_monkey_patch.rb +0 -13
  57. data/spec/support/query_counter.rb +0 -18
  58. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  59. data/spec/support_spec.rb +0 -14
  60. data/spec/tag_examples.rb +0 -665
  61. data/spec/tag_spec.rb +0 -6
  62. data/spec/user_spec.rb +0 -174
  63. data/spec/uuid_tag_spec.rb +0 -6
data/spec/tag_examples.rb DELETED
@@ -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
data/spec/tag_spec.rb DELETED
@@ -1,6 +0,0 @@
1
- require 'spec_helper'
2
- require 'tag_examples'
3
-
4
- describe Tag do
5
- it_behaves_like Tag
6
- end