closure_tree 4.6.3 → 5.0.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.
@@ -10,6 +10,9 @@ require 'closure_tree'
10
10
  require 'closure_tree/test/matcher'
11
11
  require 'tmpdir'
12
12
  require 'timecop'
13
+ require 'forwardable'
14
+ require 'securerandom'
15
+ require 'parallel'
13
16
 
14
17
  Thread.abort_on_exception = true
15
18
 
@@ -1,24 +1,26 @@
1
1
  database_folder = "#{File.dirname(__FILE__)}/../db"
2
2
  database_adapter = ENV['DB'] ||= 'mysql'
3
3
 
4
- if ENV['STDOUT_LOGGING']
5
- log = Logger.new(STDOUT)
6
- log.sev_threshold = Logger::DEBUG
7
- ActiveRecord::Base.logger = log
4
+ def sqlite?
5
+ ENV['DB'] == 'sqlite'
8
6
  end
9
7
 
8
+ log = Logger.new('db.log')
9
+ log.sev_threshold = Logger::DEBUG
10
+ ActiveRecord::Base.logger = log
11
+
10
12
  ActiveRecord::Migration.verbose = false
11
13
  ActiveRecord::Base.table_name_prefix = ENV['DB_PREFIX'].to_s
12
14
  ActiveRecord::Base.table_name_suffix = ENV['DB_SUFFIX'].to_s
15
+
16
+ def db_name
17
+ @db_name ||= "closure_tree_test_#{rand(1..2**31)}"
18
+ end
19
+
13
20
  ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read("#{database_folder}/database.yml")).result)
14
21
 
15
22
  config = ActiveRecord::Base.configurations[database_adapter]
16
23
 
17
- unless config['database'] == ':memory:'
18
- # Postgresql or Mysql
19
- config['database'].concat ENV['TRAVIS_JOB_NUMBER'].to_s.gsub(/\W/, '_')
20
- end
21
-
22
24
  begin
23
25
  case database_adapter
24
26
  when 'sqlite'
@@ -42,8 +44,8 @@ require "#{database_folder}/models"
42
44
  def count_queries(&block)
43
45
  count = 0
44
46
  counter_fn = ->(name, started, finished, unique_id, payload) do
45
- count += 1 unless payload[:name].in? %w[ CACHE SCHEMA ]
47
+ count += 1 unless payload[:name].in? %w[CACHE SCHEMA]
46
48
  end
47
- ActiveSupport::Notifications.subscribed(counter_fn, "sql.active_record", &block)
49
+ ActiveSupport::Notifications.subscribed(counter_fn, 'sql.active_record', &block)
48
50
  count
49
51
  end
@@ -33,15 +33,25 @@ shared_examples_for Tag do
33
33
  expect(tag_class.leaves).to be_empty
34
34
  end
35
35
 
36
- it "#find_or_create_by_path" do
37
- a = tag_class.create!(:name => 'a')
36
+ it "#find_or_create_by_path with strings" do
37
+ a = tag_class.create!(name: 'a')
38
38
  expect(a.find_or_create_by_path(%w{b c}).ancestry_path).to eq(%w{a b c})
39
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
40
50
  end
41
51
 
42
52
  context "with 1 tag" do
43
53
  before do
44
- @tag = tag_class.create!(:name => "tag")
54
+ @tag = tag_class.create!(name: "tag")
45
55
  end
46
56
 
47
57
  it "should be a leaf" do
@@ -64,7 +74,7 @@ shared_examples_for Tag do
64
74
 
65
75
  context "with child" do
66
76
  before do
67
- @child = tag_class.create!(:name => 'tag 2')
77
+ @child = tag_class.create!(name: 'tag 2')
68
78
  end
69
79
 
70
80
  def assert_roots_and_leaves
@@ -96,8 +106,8 @@ shared_examples_for Tag do
96
106
 
97
107
  context "with 2 tags" do
98
108
  before :each do
99
- @root = tag_class.create!(:name => "root")
100
- @leaf = @root.add_child(tag_class.create!(:name => "leaf"))
109
+ @root = tag_class.create!(name: "root")
110
+ @leaf = @root.add_child(tag_class.create!(name: "leaf"))
101
111
  end
102
112
  it "should return a simple root and leaf" do
103
113
  expect(tag_class.roots).to eq([@root])
@@ -114,9 +124,9 @@ shared_examples_for Tag do
114
124
 
115
125
  context "3 tag collection.create db" do
116
126
  before :each do
117
- @root = tag_class.create! :name => "root"
118
- @mid = @root.children.create! :name => "mid"
119
- @leaf = @mid.children.create! :name => "leaf"
127
+ @root = tag_class.create! name: "root"
128
+ @mid = @root.children.create! name: "mid"
129
+ @leaf = @mid.children.create! name: "leaf"
120
130
  DestroyedTag.delete_all
121
131
  end
122
132
 
@@ -144,7 +154,7 @@ shared_examples_for Tag do
144
154
  end
145
155
 
146
156
  it 'fix self_and_ancestors properly on reparenting' do
147
- t = tag_class.create! :name => 'moar leaf'
157
+ t = tag_class.create! name: 'moar leaf'
148
158
  expect(t.self_and_ancestors.to_a).to eq([t])
149
159
  @mid.children << t
150
160
  expect(t.self_and_ancestors.to_a).to eq([t, @mid, @root])
@@ -157,7 +167,7 @@ shared_examples_for Tag do
157
167
  end
158
168
 
159
169
  it 'moves non-leaves' do
160
- new_root = tag_class.create! :name => "new_root"
170
+ new_root = tag_class.create! name: "new_root"
161
171
  new_root.children << @mid
162
172
  expect(@root.reload.descendants).to be_empty
163
173
  expect(new_root.descendants).to eq([@mid, @leaf])
@@ -165,7 +175,7 @@ shared_examples_for Tag do
165
175
  end
166
176
 
167
177
  it 'moves leaves' do
168
- new_root = tag_class.create! :name => "new_root"
178
+ new_root = tag_class.create! name: "new_root"
169
179
  new_root.children << @leaf
170
180
  expect(new_root.descendants).to eq([@leaf])
171
181
  expect(@root.reload.descendants).to eq([@mid])
@@ -175,9 +185,9 @@ shared_examples_for Tag do
175
185
 
176
186
  context "3 tag explicit_create db" do
177
187
  before :each do
178
- @root = tag_class.create!(:name => "root")
179
- @mid = @root.add_child(tag_class.create!(:name => "mid"))
180
- @leaf = @mid.add_child(tag_class.create!(:name => "leaf"))
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"))
181
191
  end
182
192
 
183
193
  it "should create all tags" do
@@ -208,19 +218,19 @@ shared_examples_for Tag do
208
218
 
209
219
  it "cleans up hierarchy references for leaves" do
210
220
  @leaf.destroy
211
- expect(tag_hierarchy_class.where(:ancestor_id => @leaf.id)).to be_empty
212
- expect(tag_hierarchy_class.where(:descendant_id => @leaf.id)).to be_empty
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
213
223
  end
214
224
 
215
225
  it "cleans up hierarchy references" do
216
226
  @mid.destroy
217
- expect(tag_hierarchy_class.where(:ancestor_id => @mid.id)).to be_empty
218
- expect(tag_hierarchy_class.where(:descendant_id => @mid.id)).to be_empty
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
219
229
  expect(@root.reload).to be_root
220
230
  root_hiers = @root.ancestor_hierarchies.to_a
221
231
  expect(root_hiers.size).to eq(1)
222
- expect(tag_hierarchy_class.where(:ancestor_id => @root.id)).to eq(root_hiers)
223
- expect(tag_hierarchy_class.where(:descendant_id => @root.id)).to eq(root_hiers)
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)
224
234
  end
225
235
 
226
236
  it "should have different hash codes for each hierarchy model" do
@@ -234,12 +244,12 @@ shared_examples_for Tag do
234
244
  end
235
245
 
236
246
  it "performs as the readme says it does" do
237
- grandparent = tag_class.create(:name => 'Grandparent')
238
- parent = grandparent.children.create(:name => 'Parent')
239
- child1 = tag_class.create(:name => 'First Child', :parent => parent)
240
- child2 = tag_class.new(:name => 'Second Child')
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')
241
251
  parent.children << child2
242
- child3 = tag_class.new(:name => 'Third Child')
252
+ child3 = tag_class.new(name: 'Third Child')
243
253
  parent.add_child child3
244
254
  expect(grandparent.self_and_descendants.collect(&:name)).to eq(
245
255
  ["Grandparent", "Parent", "First Child", "Second Child", "Third Child"]
@@ -259,11 +269,11 @@ shared_examples_for Tag do
259
269
 
260
270
  it "roots sort alphabetically" do
261
271
  expected = ("a".."z").to_a
262
- expected.shuffle.each { |ea| tag_class.create!(:name => ea) }
272
+ expected.shuffle.each { |ea| tag_class.create!(name: ea) }
263
273
  expect(tag_class.roots.collect { |ea| ea.name }).to eq(expected)
264
274
  end
265
275
 
266
- context "with simple tree" do
276
+ context 'with simple tree' do
267
277
  before :each do
268
278
  tag_class.find_or_create_by_path %w(a1 b1 c1a)
269
279
  tag_class.find_or_create_by_path %w(a1 b1 c1b)
@@ -272,9 +282,8 @@ shared_examples_for Tag do
272
282
  tag_class.find_or_create_by_path %w(a2 b2)
273
283
  tag_class.find_or_create_by_path %w(a3)
274
284
 
275
- @a1, @a2, @a3, @b1, @b1b, @b2, @c1a, @c1b, @c1c = tag_class.
276
- where(:name => %w(a1 a2 a3 b1 b1b b2 c1a c1b c1c)).
277
- reorder(:name).to_a
285
+ @a1, @a2, @a3, @b1, @b1b, @b2, @c1a, @c1b, @c1c =
286
+ tag_class.all.sort_by(&:name)
278
287
  @expected_roots = [@a1, @a2, @a3]
279
288
  @expected_leaves = [@c1a, @c1b, @c1c, @b1b, @b2, @a3]
280
289
  @expected_siblings = [[@a1, @a2, @a3], [@b1, @b1b], [@c1a, @c1b, @c1c]]
@@ -365,40 +374,63 @@ shared_examples_for Tag do
365
374
  it 'limits subsequent where clauses' do
366
375
  a1c = tag_class.find_or_create_by_path %w(A1 B C)
367
376
  a2c = tag_class.find_or_create_by_path %w(A2 B C)
368
- expect(tag_class.where(:name => "C").to_a).to match_array([a1c, a2c])
369
- expect(tag_class.with_ancestor(a1c.parent.parent).where(:name => "C").to_a).to eq([a1c])
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])
370
381
  end
371
382
  end
372
383
 
373
- context "paths" do
374
- before :each do
375
- @child = tag_class.find_or_create_by_path(%w(grandparent parent child))
376
- @child.title = "Kid"
377
- @parent = @child.parent
378
- @parent.title = "Mom"
379
- @grandparent = @parent.parent
380
- @grandparent.title = "Nonnie"
381
- [@child, @parent, @grandparent].each { |ea| ea.save! }
382
- end
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
383
394
 
384
- it "should build ancestry path" do
385
- expect(@child.ancestry_path).to eq(%w{grandparent parent child})
386
- expect(@child.ancestry_path(:name)).to eq(%w{grandparent parent child})
387
- expect(@child.ancestry_path(:title)).to eq(%w{Nonnie Mom Kid})
388
- end
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
389
400
 
390
- it 'assembles ancestors' do
391
- expect(@child.ancestors).to eq([@parent, @grandparent])
392
- expect(@child.self_and_ancestors).to eq([@child, @parent, @grandparent])
393
- end
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
394
414
 
395
- it "should find by path" do
396
- # class method:
397
- expect(tag_class.find_by_path(%w{grandparent parent child})).to eq(@child)
398
- # instance method:
399
- expect(@parent.find_by_path(%w{child})).to eq(@child)
400
- expect(@grandparent.find_by_path(%w{parent child})).to eq(@child)
401
- expect(@parent.find_by_path(%w{child larvae})).to be_nil
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
402
434
  end
403
435
 
404
436
  it "finds correctly rooted paths" do
@@ -415,10 +447,14 @@ shared_examples_for Tag do
415
447
  end
416
448
 
417
449
  it "find_by_path for 2 nodes" do
418
- c = tag_class.find_or_create_by_path %w(a b c)
419
- expect(c.root.find_by_path(%w(b c))).to eq(c)
420
- expect(c.root.find_by_path(%w(a c))).to be_nil
421
- expect(c.root.find_by_path(%w(c))).to be_nil
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
422
458
  end
423
459
 
424
460
  it "find_by_path for 3 nodes" do
@@ -435,32 +471,29 @@ shared_examples_for Tag do
435
471
  expect(tag_class.find_by_path(%w{grandparent parent missing child})).to be_nil
436
472
  end
437
473
 
438
- it ".find_or_create_by_path" do
439
- grandparent = tag_class.find_or_create_by_path(%w{grandparent})
440
- expect(grandparent).to eq(@grandparent)
441
- child = tag_class.find_or_create_by_path(%w{grandparent parent child})
442
- expect(child).to eq(@child)
443
- expect(tag_class.find_or_create_by_path(%w{events anniversary}).ancestry_path).to eq(%w{events anniversary})
444
- end
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
445
481
 
446
- it "should respect attribute hashes with both selection and creation" do
447
- expected_title = 'something else'
448
- attrs = {:title => expected_title}
449
- existing_title = @grandparent.title
450
- new_grandparent = tag_class.find_or_create_by_path(%w{grandparent}, attrs)
451
- expect(new_grandparent).not_to eq(@grandparent)
452
- expect(new_grandparent.title).to eq(expected_title)
453
- expect(@grandparent.reload.title).to eq(existing_title)
454
- end
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
455
486
 
456
- it "should create a hierarchy with a given attribute" do
457
- expected_title = 'unicorn rainbows'
458
- attrs = {:title => expected_title}
459
- child = tag_class.find_or_create_by_path(%w{grandparent parent child}, attrs)
460
- expect(child).not_to eq(@child)
461
- [child, child.parent, child.parent.parent].each do |ea|
462
- expect(ea.title).to eq(expected_title)
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})
463
495
  end
496
+
464
497
  end
465
498
  end
466
499
 
@@ -480,19 +513,19 @@ shared_examples_for Tag do
480
513
 
481
514
  context "#hash_tree" do
482
515
  it "returns {} for depth 0" do
483
- expect(tag_class.hash_tree(:limit_depth => 0)).to eq({})
516
+ expect(tag_class.hash_tree(limit_depth: 0)).to eq({})
484
517
  end
485
518
  it "limit_depth 1" do
486
- expect(tag_class.hash_tree(:limit_depth => 1)).to eq({@a => {}})
519
+ expect(tag_class.hash_tree(limit_depth: 1)).to eq({@a => {}})
487
520
  end
488
521
  it "limit_depth 2" do
489
- expect(tag_class.hash_tree(:limit_depth => 2)).to eq({@a => {@b => {}, @b2 => {}}})
522
+ expect(tag_class.hash_tree(limit_depth: 2)).to eq({@a => {@b => {}, @b2 => {}}})
490
523
  end
491
524
  it "limit_depth 3" do
492
- expect(tag_class.hash_tree(:limit_depth => 3)).to eq({@a => {@b => {@c1 => {}, @c2 => {}}, @b2 => {}}})
525
+ expect(tag_class.hash_tree(limit_depth: 3)).to eq({@a => {@b => {@c1 => {}, @c2 => {}}, @b2 => {}}})
493
526
  end
494
527
  it "limit_depth 4" do
495
- expect(tag_class.hash_tree(:limit_depth => 4)).to eq(@full_tree)
528
+ expect(tag_class.hash_tree(limit_depth: 4)).to eq(@full_tree)
496
529
  end
497
530
  it "no limit holdum" do
498
531
  expect(tag_class.hash_tree).to eq(@full_tree)
@@ -532,16 +565,16 @@ shared_examples_for Tag do
532
565
  before :each do
533
566
  end
534
567
  it "returns {} for depth 0" do
535
- expect(@b.hash_tree(:limit_depth => 0)).to eq({})
568
+ expect(@b.hash_tree(limit_depth: 0)).to eq({})
536
569
  end
537
570
  it "limit_depth 1" do
538
- expect(@b.hash_tree(:limit_depth => 1)).to eq({@b => {}})
571
+ expect(@b.hash_tree(limit_depth: 1)).to eq({@b => {}})
539
572
  end
540
573
  it "limit_depth 2" do
541
- expect(@b.hash_tree(:limit_depth => 2)).to eq({@b => {@c1 => {}, @c2 => {}}})
574
+ expect(@b.hash_tree(limit_depth: 2)).to eq({@b => {@c1 => {}, @c2 => {}}})
542
575
  end
543
576
  it "limit_depth 3" do
544
- expect(@b.hash_tree(:limit_depth => 3)).to eq({@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}})
577
+ expect(@b.hash_tree(limit_depth: 3)).to eq({@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}})
545
578
  end
546
579
  it "no limit holdum from subsubroot" do
547
580
  expect(@c1.hash_tree).to eq({@c1 => {@d1 => {}}})
@@ -555,12 +588,14 @@ shared_examples_for Tag do
555
588
  end
556
589
  end
557
590
 
558
- describe 'very deep trees' do
559
- it 'should find_or_create very deep nodes' do
560
- expected_ancestry_path = (1..200).to_a.map { |ea| ea.to_s }
561
- target = tag_class.find_or_create_by_path(expected_ancestry_path)
562
- expect(target.ancestry_path).to eq(expected_ancestry_path)
563
- end
591
+ it 'finds_by_path for very deep trees' do
592
+ expect(tag_class._ct).to receive(:max_join_tables).at_least(1).and_return(3)
593
+ path = (1..20).to_a.map { |ea| ea.to_s }
594
+ subject = tag_class.find_or_create_by_path(path)
595
+ expect(subject.ancestry_path).to eq(path)
596
+ expect(tag_class.find_by_path(path)).to eq(subject)
597
+ root = subject.root
598
+ expect(root.find_by_path(path[1..-1])).to eq(subject)
564
599
  end
565
600
 
566
601
  describe 'DOT rendering' do
@@ -571,7 +606,7 @@ shared_examples_for Tag do
571
606
  tag_class.find_or_create_by_path(%w(a b1 c1))
572
607
  tag_class.find_or_create_by_path(%w(a b2 c2))
573
608
  tag_class.find_or_create_by_path(%w(a b2 c3))
574
- a, b1, b2, c1, c2, c3 = %w(a b1 b2 c1 c2 c3).map { |ea| tag_class.where(:name => ea).first.id }
609
+ a, b1, b2, c1, c2, c3 = %w(a b1 b2 c1 c2 c3).map { |ea| tag_class.where(name: ea).first.id }
575
610
  dot = tag_class.roots.first.to_dot_digraph
576
611
  expect(dot).to eq <<-DOT
577
612
  digraph G {