closure_tree 4.6.3 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 {