closure_tree 6.4.0 → 7.2.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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +19 -12
  4. data/Appraisals +75 -7
  5. data/CHANGELOG.md +92 -39
  6. data/Gemfile +0 -12
  7. data/README.md +67 -24
  8. data/_config.yml +1 -0
  9. data/closure_tree.gemspec +10 -7
  10. data/lib/closure_tree/finders.rb +32 -9
  11. data/lib/closure_tree/has_closure_tree.rb +4 -0
  12. data/lib/closure_tree/has_closure_tree_root.rb +4 -6
  13. data/lib/closure_tree/hash_tree_support.rb +4 -4
  14. data/lib/closure_tree/hierarchy_maintenance.rb +31 -11
  15. data/lib/closure_tree/model.rb +42 -16
  16. data/lib/closure_tree/numeric_deterministic_ordering.rb +20 -6
  17. data/lib/closure_tree/numeric_order_support.rb +7 -3
  18. data/lib/closure_tree/support.rb +18 -12
  19. data/lib/closure_tree/support_attributes.rb +10 -1
  20. data/lib/closure_tree/support_flags.rb +1 -4
  21. data/lib/closure_tree/version.rb +1 -1
  22. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  23. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  24. metadata +29 -75
  25. data/gemfiles/activerecord_4.2.gemfile +0 -19
  26. data/gemfiles/activerecord_5.0.gemfile +0 -19
  27. data/gemfiles/activerecord_5.0_foreigner.gemfile +0 -20
  28. data/gemfiles/activerecord_edge.gemfile +0 -20
  29. data/img/example.png +0 -0
  30. data/img/preorder.png +0 -0
  31. data/spec/cache_invalidation_spec.rb +0 -39
  32. data/spec/cuisine_type_spec.rb +0 -38
  33. data/spec/db/database.yml +0 -21
  34. data/spec/db/models.rb +0 -128
  35. data/spec/db/schema.rb +0 -166
  36. data/spec/fixtures/tags.yml +0 -98
  37. data/spec/generators/migration_generator_spec.rb +0 -48
  38. data/spec/has_closure_tree_root_spec.rb +0 -154
  39. data/spec/hierarchy_maintenance_spec.rb +0 -16
  40. data/spec/label_spec.rb +0 -554
  41. data/spec/matcher_spec.rb +0 -34
  42. data/spec/metal_spec.rb +0 -55
  43. data/spec/model_spec.rb +0 -9
  44. data/spec/namespace_type_spec.rb +0 -13
  45. data/spec/parallel_spec.rb +0 -159
  46. data/spec/spec_helper.rb +0 -24
  47. data/spec/support/database.rb +0 -52
  48. data/spec/support/database_cleaner.rb +0 -14
  49. data/spec/support/exceed_query_limit.rb +0 -18
  50. data/spec/support/hash_monkey_patch.rb +0 -13
  51. data/spec/support/query_counter.rb +0 -18
  52. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  53. data/spec/support_spec.rb +0 -14
  54. data/spec/tag_examples.rb +0 -665
  55. data/spec/tag_spec.rb +0 -6
  56. data/spec/user_spec.rb +0 -174
  57. data/spec/uuid_tag_spec.rb +0 -6
@@ -1,98 +0,0 @@
1
- # Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
2
-
3
- grandparent:
4
- name: grandparent
5
- title: Nonnie
6
-
7
- parent:
8
- name: parent
9
- parent: grandparent
10
- title: Mom
11
-
12
- child:
13
- name: child
14
- parent: parent
15
- title: Kid
16
-
17
- people:
18
- name: people
19
-
20
- # people has no children
21
-
22
- events:
23
- name: events
24
-
25
- # events has only one child
26
-
27
- birthday:
28
- name: birthday
29
- parent: events
30
-
31
- places:
32
- name: places
33
-
34
- # places has many children, with many depths
35
-
36
- home:
37
- name: home
38
- parent: places
39
-
40
- indoor:
41
- name: indoor
42
- parent: places
43
-
44
- outdoor:
45
- name: outdoor
46
- parent: places
47
-
48
- museum:
49
- name: museum
50
- parent: places
51
-
52
- united_states:
53
- name: united_states
54
- parent: places
55
-
56
- california:
57
- name: california
58
- parent: united_states
59
-
60
- san_francisco:
61
- name: san_francisco
62
- parent: california
63
-
64
-
65
- # Move and deletion test tree
66
-
67
- a1:
68
- name: a1
69
-
70
- b1:
71
- name: b1
72
- parent: a1
73
-
74
- b2:
75
- name: b2
76
- parent: a1
77
-
78
- c1a:
79
- name: c1a
80
- parent: b1
81
- sort_order: 2
82
-
83
- c1b:
84
- name: c1b
85
- parent: b1
86
- sort_order: 1
87
-
88
- c2:
89
- name: c2
90
- parent: b2
91
-
92
- d2:
93
- name: d2
94
- parent: c2
95
-
96
- e2:
97
- name: e2
98
- parent: d2
@@ -1,48 +0,0 @@
1
- # require 'spec_helper'
2
- # require 'ammeter/init'
3
- #
4
- # # Generators are not automatically loaded by Rails
5
- # require 'generators/closure_tree/migration_generator'
6
- #
7
- # RSpec.describe ClosureTree::Generators::MigrationGenerator, type: :generator do
8
- # TMPDIR = Dir.mktmpdir
9
- # # Tell generator where to put its output
10
- # destination TMPDIR
11
- # before { prepare_destination }
12
- #
13
- # describe 'generator output' do
14
- # before { run_generator %w(tag) }
15
- # subject { migration_file('db/migrate/create_tag_hierarchies.rb') }
16
- # it { is_expected.to be_a_migration }
17
- # it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
18
- # it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
19
- # it { is_expected.to contain(/t.integer :generations, null: false/) }
20
- # it { is_expected.to contain(/add_index :tag_hierarchies/) }
21
- # end
22
- #
23
- # describe 'generator output with namespaced model' do
24
- # before { run_generator %w(Namespace::Type) }
25
- # subject { migration_file('db/migrate/create_namespace_type_hierarchies.rb') }
26
- # it { is_expected.to be_a_migration }
27
- # it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
28
- # it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
29
- # it { is_expected.to contain(/t.integer :generations, null: false/) }
30
- # it { is_expected.to contain(/add_index :namespace_type_hierarchies/) }
31
- # end
32
- #
33
- # describe 'generator output with namespaced model with /' do
34
- # before { run_generator %w(namespace/type) }
35
- # subject { migration_file('db/migrate/create_namespace_type_hierarchies.rb') }
36
- # it { is_expected.to be_a_migration }
37
- # it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
38
- # it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
39
- # it { is_expected.to contain(/t.integer :generations, null: false/) }
40
- # it { is_expected.to contain(/add_index :namespace_type_hierarchies/) }
41
- # end
42
- #
43
- # it 'should run all tasks in generator without errors' do
44
- # gen = generator %w(tag)
45
- # expect(gen).to receive :create_migration_file
46
- # capture(:stdout) { gen.invoke_all }
47
- # end
48
- # end
@@ -1,154 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "has_closure_tree_root" do
4
- let!(:ct1) { ContractType.create!(name: "Type1") }
5
- let!(:ct2) { ContractType.create!(name: "Type2") }
6
- let!(:user1) { User.create!(email: "1@example.com", group_id: group.id) }
7
- let!(:user2) { User.create!(email: "2@example.com", group_id: group.id) }
8
- let!(:user3) { User.create!(email: "3@example.com", group_id: group.id) }
9
- let!(:user4) { User.create!(email: "4@example.com", group_id: group.id) }
10
- let!(:user5) { User.create!(email: "5@example.com", group_id: group.id) }
11
- let!(:user6) { User.create!(email: "6@example.com", group_id: group.id) }
12
- let!(:group_reloaded) { group.class.find(group.id) } # Ensures were starting fresh.
13
-
14
- before do
15
- # The tree (contract types in parens)
16
- #
17
- # U1(1)
18
- # / \
19
- # U2(1) U3(1&2)
20
- # / / \
21
- # U4(2) U5(1) U6(2)
22
-
23
- user1.children << user2
24
- user1.children << user3
25
- user2.children << user4
26
- user3.children << user5
27
- user3.children << user6
28
-
29
- user1.contracts.create!(title: "Contract 1", contract_type: ct1)
30
- user2.contracts.create!(title: "Contract 2", contract_type: ct1)
31
- user3.contracts.create!(title: "Contract 3", contract_type: ct1)
32
- user3.contracts.create!(title: "Contract 4", contract_type: ct2)
33
- user4.contracts.create!(title: "Contract 5", contract_type: ct2)
34
- user5.contracts.create!(title: "Contract 6", contract_type: ct1)
35
- user6.contracts.create!(title: "Contract 7", contract_type: ct2)
36
- end
37
-
38
- context "with basic config" do
39
- let!(:group) { Group.create!(name: "TheGroup") }
40
-
41
- it "loads all nodes in a constant number of queries" do
42
- expect do
43
- root = group_reloaded.root_user_including_tree
44
- expect(root.children[0].email).to eq "2@example.com"
45
- expect(root.children[0].parent.children[1].email).to eq "3@example.com"
46
- end.to_not exceed_query_limit(2)
47
- end
48
-
49
- it "loads all nodes plus single association in a constant number of queries" do
50
- expect do
51
- root = group_reloaded.root_user_including_tree(:contracts)
52
- expect(root.children[0].email).to eq "2@example.com"
53
- expect(root.children[0].parent.children[1].email).to eq "3@example.com"
54
- expect(root.children[0].children[0].contracts[0].user.
55
- parent.parent.children[1].children[1].contracts[0].title).to eq "Contract 7"
56
- end.to_not exceed_query_limit(3)
57
- end
58
-
59
- it "loads all nodes and associations in a constant number of queries" do
60
- expect do
61
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
62
- expect(root.children[0].email).to eq "2@example.com"
63
- expect(root.children[0].parent.children[1].email).to eq "3@example.com"
64
- expect(root.children[1].contracts.map(&:contract_type).map(&:name)).to eq %w(Type1 Type2)
65
- expect(root.children[1].children[0].contracts[0].contract_type.name).to eq "Type1"
66
- expect(root.children[0].children[0].contracts[0].user.
67
- parent.parent.children[1].children[1].contracts[0].contract_type.name).to eq "Type2"
68
- end.to_not exceed_query_limit(4) # Without this feature, this is 15, and scales with number of nodes.
69
- end
70
-
71
- it "memoizes by assoc_map" do
72
- group_reloaded.root_user_including_tree.email = "x"
73
- expect(group_reloaded.root_user_including_tree.email).to eq "x"
74
- group_reloaded.root_user_including_tree(contracts: :contract_type).email = "y"
75
- expect(group_reloaded.root_user_including_tree(contracts: :contract_type).email).to eq "y"
76
- expect(group_reloaded.root_user_including_tree.email).to eq "x"
77
- end
78
-
79
- it "doesn't memoize if true argument passed" do
80
- group_reloaded.root_user_including_tree.email = "x"
81
- expect(group_reloaded.root_user_including_tree(true).email).to eq "1@example.com"
82
- group_reloaded.root_user_including_tree(contracts: :contract_type).email = "y"
83
- expect(group_reloaded.root_user_including_tree(true, contracts: :contract_type).email).
84
- to eq "1@example.com"
85
- end
86
-
87
- it "works if true passed on first call" do
88
- expect(group_reloaded.root_user_including_tree(true).email).to eq "1@example.com"
89
- end
90
-
91
- it "eager loads inverse association to group" do
92
- expect do
93
- root = group_reloaded.root_user_including_tree
94
- expect(root.group).to eq group
95
- expect(root.children[0].group).to eq group
96
- end.to_not exceed_query_limit(2)
97
- end
98
-
99
- it "works if eager load association map is not given" do
100
- expect do
101
- root = group_reloaded.root_user_including_tree
102
- expect(root.children[0].email).to eq "2@example.com"
103
- expect(root.children[0].parent.children[1].children[0].email).to eq "5@example.com"
104
- end.to_not exceed_query_limit(2)
105
- end
106
-
107
- context "with no tree root" do
108
- let(:group2) { Group.create!(name: "OtherGroup") }
109
-
110
- it "should return nil" do
111
- expect(group2.root_user_including_tree(contracts: :contract_type)).to be_nil
112
- end
113
- end
114
-
115
- context "with multiple tree roots" do
116
- let!(:other_root) { User.create!(email: "10@example.com", group_id: group.id) }
117
-
118
- it "should error" do
119
- expect do
120
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
121
- end.to raise_error(ClosureTree::MultipleRootError)
122
- end
123
- end
124
- end
125
-
126
- context "with explicit class_name and foreign_key" do
127
- let(:group) { Grouping.create!(name: "TheGrouping") }
128
-
129
- it "should still work" do
130
- root = group_reloaded.root_person_including_tree(contracts: :contract_type)
131
- expect(root.children[0].email).to eq "2@example.com"
132
- end
133
- end
134
-
135
- context "with bad class_name" do
136
- let(:group) { UserSet.create!(name: "TheUserSet") }
137
-
138
- it "should error" do
139
- expect do
140
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
141
- end.to raise_error(NameError)
142
- end
143
- end
144
-
145
- context "with bad foreign_key" do
146
- let(:group) { Team.create!(name: "TheTeam") }
147
-
148
- it "should error" do
149
- expect do
150
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
151
- end.to raise_error(ActiveRecord::StatementInvalid)
152
- end
153
- end
154
- end
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ClosureTree::HierarchyMaintenance do
4
- describe '.rebuild!' do
5
- it 'rebuild tree' do
6
- 20.times do |counter|
7
- Metal.create(:value => "Nitro-#{counter}", parent: Metal.all.sample)
8
- end
9
- hierarchy_count = MetalHierarchy.count
10
- expect(hierarchy_count).to be > (20*2)-1 # shallowest-possible case, where all children use the first root
11
- MetalHierarchy.delete_all
12
- Metal.rebuild!
13
- expect(MetalHierarchy.count).to eq(hierarchy_count)
14
- end
15
- end
16
- end
@@ -1,554 +0,0 @@
1
- require 'spec_helper'
2
-
3
- def create_label_tree
4
- @d1 = Label.find_or_create_by_path %w(a1 b1 c1 d1)
5
- @c1 = @d1.parent
6
- @b1 = @c1.parent
7
- @a1 = @b1.parent
8
- @d2 = Label.find_or_create_by_path %w(a1 b1 c2 d2)
9
- @c2 = @d2.parent
10
- @d3 = Label.find_or_create_by_path %w(a2 b2 c3 d3)
11
- @c3 = @d3.parent
12
- @b2 = @c3.parent
13
- @a2 = @b2.parent
14
- Label.update_all("#{Label._ct.order_column} = id")
15
- end
16
-
17
- def create_preorder_tree(suffix = "", &block)
18
- %w(
19
- a/l/n/r
20
- a/l/n/q
21
- a/l/n/p
22
- a/l/n/o
23
- a/l/m
24
- a/b/h/i/j/k
25
- a/b/c/d/g
26
- a/b/c/d/f
27
- a/b/c/d/e
28
- ).shuffle.each { |ea| Label.find_or_create_by_path(ea.split('/').collect { |ea| "#{ea}#{suffix}" }) }
29
-
30
- Label.roots.each_with_index do |root, root_idx|
31
- root.order_value = root_idx
32
- yield(root) if block_given?
33
- root.save!
34
- root.self_and_descendants.each do |ea|
35
- ea.children.to_a.sort_by(&:name).each_with_index do |ea, idx|
36
- ea.order_value = idx
37
- yield(ea) if block_given?
38
- ea.save!
39
- end
40
- end
41
- end
42
- end
43
-
44
- describe Label do
45
- context "destruction" do
46
- it "properly destroys descendents created with find_or_create_by_path" do
47
- c = Label.find_or_create_by_path %w(a b c)
48
- b = c.parent
49
- a = c.root
50
- a.destroy
51
- expect(Label.exists?(id: [a.id, b.id, c.id])).to be_falsey
52
- end
53
-
54
- it "properly destroys descendents created with add_child" do
55
- a = Label.create(name: 'a')
56
- b = a.add_child Label.new(name: 'b')
57
- c = b.add_child Label.new(name: 'c')
58
- a.destroy
59
- expect(Label.exists?(a.id)).to be_falsey
60
- expect(Label.exists?(b.id)).to be_falsey
61
- expect(Label.exists?(c.id)).to be_falsey
62
- end
63
-
64
- it "properly destroys descendents created with <<" do
65
- a = Label.create(name: 'a')
66
- b = Label.new(name: 'b')
67
- a.children << b
68
- c = Label.new(name: 'c')
69
- b.children << c
70
- a.destroy
71
- expect(Label.exists?(a.id)).to be_falsey
72
- expect(Label.exists?(b.id)).to be_falsey
73
- expect(Label.exists?(c.id)).to be_falsey
74
- end
75
- end
76
-
77
- context "roots" do
78
- it "sorts alphabetically" do
79
- expected = (0..10).to_a
80
- expected.shuffle.each do |ea|
81
- Label.create! do |l|
82
- l.name = "root #{ea}"
83
- l.order_value = ea
84
- end
85
- end
86
- expect(Label.roots.collect { |ea| ea.order_value }).to eq(expected)
87
- end
88
- end
89
-
90
- context "Base Label class" do
91
- it "should find or create by path" do
92
- # class method:
93
- c = Label.find_or_create_by_path(%w{grandparent parent child})
94
- expect(c.ancestry_path).to eq(%w{grandparent parent child})
95
- expect(c.name).to eq("child")
96
- expect(c.parent.name).to eq("parent")
97
- end
98
- end
99
-
100
- context "Parent/child inverse relationships" do
101
- it "should associate both sides of the parent and child relationships" do
102
- parent = Label.new(:name => 'parent')
103
- child = parent.children.build(:name => 'child')
104
- expect(parent).to be_root
105
- expect(parent).not_to be_leaf
106
- expect(child).not_to be_root
107
- expect(child).to be_leaf
108
- end
109
- end
110
-
111
- context "DateLabel" do
112
- it "should find or create by path" do
113
- date = DateLabel.find_or_create_by_path(%w{2011 November 23})
114
- expect(date.ancestry_path).to eq(%w{2011 November 23})
115
- date.self_and_ancestors.each { |ea| expect(ea.class).to eq(DateLabel) }
116
- expect(date.name).to eq("23")
117
- expect(date.parent.name).to eq("November")
118
- end
119
- end
120
-
121
- context "DirectoryLabel" do
122
- it "should find or create by path" do
123
- dir = DirectoryLabel.find_or_create_by_path(%w{grandparent parent child})
124
- expect(dir.ancestry_path).to eq(%w{grandparent parent child})
125
- expect(dir.name).to eq("child")
126
- expect(dir.parent.name).to eq("parent")
127
- expect(dir.parent.parent.name).to eq("grandparent")
128
- expect(dir.root.name).to eq("grandparent")
129
- expect(dir.id).not_to eq(Label.find_or_create_by_path(%w{grandparent parent child}))
130
- dir.self_and_ancestors.each { |ea| expect(ea.class).to eq(DirectoryLabel) }
131
- end
132
- end
133
-
134
- context "Mixed class tree" do
135
- context "preorder tree" do
136
- before do
137
- classes = [Label, DateLabel, DirectoryLabel, EventLabel]
138
- create_preorder_tree do |ea|
139
- ea.type = classes[ea.order_value % 4].to_s
140
- end
141
- end
142
- it "finds roots with specific classes" do
143
- expect(Label.roots).to eq(Label.where(:name => 'a').to_a)
144
- expect(DirectoryLabel.roots).to be_empty
145
- expect(EventLabel.roots).to be_empty
146
- end
147
-
148
- it "all is limited to subclasses" do
149
- expect(DateLabel.all.map(&:name)).to match_array(%w(f h l n p))
150
- expect(DirectoryLabel.all.map(&:name)).to match_array(%w(g q))
151
- expect(EventLabel.all.map(&:name)).to eq(%w(r))
152
- end
153
-
154
- it "returns descendents regardless of subclass" do
155
- expect(Label.root.descendants.map { |ea| ea.class.to_s }.uniq).to match_array(
156
- %w(Label DateLabel DirectoryLabel EventLabel)
157
- )
158
- end
159
- end
160
-
161
- it "supports children << and add_child" do
162
- a = EventLabel.create!(:name => "a")
163
- b = DateLabel.new(:name => "b")
164
- a.children << b
165
- c = Label.new(:name => "c")
166
- b.add_child(c)
167
-
168
- expect(a.self_and_descendants.collect do |ea|
169
- ea.class
170
- end).to eq([EventLabel, DateLabel, Label])
171
-
172
- expect(a.self_and_descendants.collect do |ea|
173
- ea.name
174
- end).to eq(%w(a b c))
175
- end
176
- end
177
-
178
- context "find_all_by_generation" do
179
- before :each do
180
- create_label_tree
181
- end
182
-
183
- it "finds roots from the class method" do
184
- expect(Label.find_all_by_generation(0).to_a).to eq([@a1, @a2])
185
- end
186
-
187
- it "finds roots from themselves" do
188
- expect(@a1.find_all_by_generation(0).to_a).to eq([@a1])
189
- end
190
-
191
- it "finds itself for non-roots" do
192
- expect(@b1.find_all_by_generation(0).to_a).to eq([@b1])
193
- end
194
-
195
- it "finds children for roots" do
196
- expect(Label.find_all_by_generation(1).to_a).to eq([@b1, @b2])
197
- end
198
-
199
- it "finds children" do
200
- expect(@a1.find_all_by_generation(1).to_a).to eq([@b1])
201
- expect(@b1.find_all_by_generation(1).to_a).to eq([@c1, @c2])
202
- end
203
-
204
- it "finds grandchildren for roots" do
205
- expect(Label.find_all_by_generation(2).to_a).to eq([@c1, @c2, @c3])
206
- end
207
-
208
- it "finds grandchildren" do
209
- expect(@a1.find_all_by_generation(2).to_a).to eq([@c1, @c2])
210
- expect(@b1.find_all_by_generation(2).to_a).to eq([@d1, @d2])
211
- end
212
-
213
- it "finds great-grandchildren for roots" do
214
- expect(Label.find_all_by_generation(3).to_a).to eq([@d1, @d2, @d3])
215
- end
216
- end
217
-
218
- context "loading through self_and_ scopes" do
219
- before :each do
220
- create_label_tree
221
- end
222
-
223
- it "self_and_descendants should result in one select" do
224
- expect(count_queries do
225
- a1_array = @a1.self_and_descendants
226
- expect(a1_array.collect { |ea| ea.name }).to eq(%w(a1 b1 c1 c2 d1 d2))
227
- end).to eq(1)
228
- end
229
-
230
- it "self_and_ancestors should result in one select" do
231
- expect(count_queries do
232
- d1_array = @d1.self_and_ancestors
233
- expect(d1_array.collect { |ea| ea.name }).to eq(%w(d1 c1 b1 a1))
234
- end).to eq(1)
235
- end
236
- end
237
-
238
- context "deterministically orders with polymorphic siblings" do
239
- before :each do
240
- @parent = Label.create!(:name => 'parent')
241
- @a, @b, @c, @d, @e, @f = ('a'..'f').map { |ea| EventLabel.new(:name => ea) }
242
- @parent.children << @a
243
- @a.append_sibling(@b)
244
- @b.append_sibling(@c)
245
- @c.append_sibling(@d)
246
- @parent.append_sibling(@e)
247
- @e.append_sibling(@f)
248
- end
249
-
250
- def name_and_order(enum)
251
- enum.map { |ea| [ea.name, ea.order_value] }
252
- end
253
-
254
- def children_name_and_order
255
- name_and_order(@parent.children.reload)
256
- end
257
-
258
- def roots_name_and_order
259
- name_and_order(Label.roots)
260
- end
261
-
262
- it 'order_values properly' do
263
- expect(children_name_and_order).to eq([['a', 0], ['b', 1], ['c', 2], ['d', 3]])
264
- end
265
-
266
- it 'when inserted before' do
267
- @b.append_sibling(@a)
268
- expect(children_name_and_order).to eq([['b', 0], ['a', 1], ['c', 2], ['d', 3]])
269
- end
270
-
271
- it 'when inserted after' do
272
- @a.append_sibling(@c)
273
- expect(children_name_and_order).to eq([['a', 0], ['c', 1], ['b', 2], ['d', 3]])
274
- end
275
-
276
- it 'when inserted before the first' do
277
- @a.prepend_sibling(@d)
278
- expect(children_name_and_order).to eq([['d', 0], ['a', 1], ['b', 2], ['c', 3]])
279
- end
280
-
281
- it 'when inserted after the last' do
282
- @d.append_sibling(@b)
283
- expect(children_name_and_order).to eq([['a', 0], ['c', 1], ['d', 2], ['b', 3]])
284
- end
285
-
286
- it 'prepends to root nodes' do
287
- @parent.prepend_sibling(@f)
288
- expect(roots_name_and_order).to eq([['f', 0], ['parent', 1], ['e', 2]])
289
- end
290
- end
291
-
292
- describe 'code in the readme' do
293
- it 'creates STI label hierarchies' do
294
- child = Label.find_or_create_by_path([
295
- {type: 'DateLabel', name: '2014'},
296
- {type: 'DateLabel', name: 'August'},
297
- {type: 'DateLabel', name: '5'},
298
- {type: 'EventLabel', name: 'Visit the Getty Center'}
299
- ])
300
- expect(child).to be_a(EventLabel)
301
- expect(child.name).to eq('Visit the Getty Center')
302
- expect(child.ancestors.map(&:name)).to eq(%w(5 August 2014))
303
- expect(child.ancestors.map(&:class)).to eq([DateLabel, DateLabel, DateLabel])
304
- end
305
-
306
- it 'appends and prepends siblings' do
307
- root = Label.create(name: 'root')
308
- a = root.append_child(Label.new(name: 'a'))
309
- b = Label.create(name: 'b')
310
- c = Label.create(name: 'c')
311
-
312
- a.append_sibling(b)
313
- expect(a.self_and_siblings.collect(&:name)).to eq(%w(a b))
314
- expect(root.reload.children.collect(&:name)).to eq(%w(a b))
315
- expect(root.children.collect(&:order_value)).to eq([0, 1])
316
-
317
- a.prepend_sibling(b)
318
- expect(a.self_and_siblings.collect(&:name)).to eq(%w(b a))
319
- expect(root.reload.children.collect(&:name)).to eq(%w(b a))
320
- expect(root.children.collect(&:order_value)).to eq([0, 1])
321
-
322
- a.append_sibling(c)
323
- expect(a.self_and_siblings.collect(&:name)).to eq(%w(b a c))
324
- expect(root.reload.children.collect(&:name)).to eq(%w(b a c))
325
- expect(root.children.collect(&:order_value)).to eq([0, 1, 2])
326
-
327
- # We need to reload b because it was updated by a.append_sibling(c)
328
- b.reload.append_sibling(c)
329
- expect(root.reload.children.collect(&:name)).to eq(%w(b c a))
330
- expect(root.children.collect(&:order_value)).to eq([0, 1, 2])
331
-
332
- # We need to reload a because it was updated by b.append_sibling(c)
333
- d = a.reload.append_sibling(Label.new(:name => "d"))
334
- expect(d.self_and_siblings.collect(&:name)).to eq(%w(b c a d))
335
- expect(d.self_and_siblings.collect(&:order_value)).to eq([0, 1, 2, 3])
336
- end
337
- end
338
-
339
- # https://github.com/mceachen/closure_tree/issues/84
340
- it "properly appends children with <<" do
341
- root = Label.create(:name => "root")
342
- a = Label.create(:name => "a", :parent => root)
343
- b = Label.create(:name => "b", :parent => root)
344
- expect(a.order_value).to eq(0)
345
- expect(b.order_value).to eq(1)
346
- #c = Label.create(:name => "c")
347
-
348
- # should the order_value for roots be set?
349
- expect(root.order_value).not_to be_nil
350
- expect(root.order_value).to eq(0)
351
-
352
- # order_value should never be nil on a child.
353
- expect(a.order_value).not_to be_nil
354
- expect(a.order_value).to eq(0)
355
- # Add a child to root at end of children.
356
- root.children << b
357
- expect(b.parent).to eq(root)
358
- expect(a.self_and_siblings.collect(&:name)).to eq(%w(a b))
359
- expect(root.reload.children.collect(&:name)).to eq(%w(a b))
360
- expect(root.children.collect(&:order_value)).to eq([0, 1])
361
- end
362
-
363
- context "#add_sibling" do
364
- it "should move a node before another node which has an uninitialized order_value" do
365
- f = Label.find_or_create_by_path %w(a b c d e fa)
366
- f0 = f.prepend_sibling(Label.new(:name => "fb")) # < not alpha sort, so name shouldn't matter
367
- expect(f0.ancestry_path).to eq(%w(a b c d e fb))
368
- expect(f.siblings_before.to_a).to eq([f0])
369
- expect(f0.siblings_before).to be_empty
370
- expect(f0.siblings_after).to eq([f])
371
- expect(f.siblings_after).to be_empty
372
- expect(f0.self_and_siblings).to eq([f0, f])
373
- expect(f.self_and_siblings).to eq([f0, f])
374
- end
375
-
376
- let(:f1) { Label.find_or_create_by_path %w(a1 b1 c1 d1 e1 f1) }
377
-
378
- it "should move a node to another tree" do
379
- f2 = Label.find_or_create_by_path %w(a2 b2 c2 d2 e2 f2)
380
- f1.add_sibling(f2)
381
- expect(f2.ancestry_path).to eq(%w(a1 b1 c1 d1 e1 f2))
382
- expect(f1.parent.reload.children).to eq([f1, f2])
383
- end
384
-
385
- it "should reorder old-parent siblings when a node moves to another tree" do
386
- f2 = Label.find_or_create_by_path %w(a2 b2 c2 d2 e2 f2)
387
- f3 = f2.prepend_sibling(Label.new(:name => "f3"))
388
- f4 = f2.append_sibling(Label.new(:name => "f4"))
389
- f1.add_sibling(f2)
390
- expect(f1.self_and_siblings.collect(&:order_value)).to eq([0, 1])
391
- expect(f3.self_and_siblings.collect(&:order_value)).to eq([0, 1])
392
- expect(f1.self_and_siblings.collect(&:name)).to eq(%w(f1 f2))
393
- expect(f3.self_and_siblings.collect(&:name)).to eq(%w(f3 f4))
394
- end
395
- end
396
-
397
- context "order_value must be set" do
398
-
399
- before do
400
- @root = Label.create(name: 'root')
401
- @a, @b, @c = %w(a b c).map { |n| @root.children.create(name: n) }
402
- end
403
-
404
- it 'should set order_value on roots' do
405
- expect(@root.order_value).to eq(0)
406
- end
407
-
408
- it 'should set order_value with siblings' do
409
- expect(@a.order_value).to eq(0)
410
- expect(@b.order_value).to eq(1)
411
- expect(@c.order_value).to eq(2)
412
- end
413
-
414
- it 'should reset order_value when a node is moved to another location' do
415
- root2 = Label.create(name: 'root2')
416
- root2.add_child @b
417
- expect(@a.order_value).to eq(0)
418
- expect(@b.order_value).to eq(0)
419
- expect(@c.reload.order_value).to eq(1)
420
- end
421
- end
422
-
423
- context "destructive reordering" do
424
- before :each do
425
- # to make sure order_value isn't affected by additional nodes:
426
- create_preorder_tree
427
- @root = Label.create(:name => 'root')
428
- @a = @root.children.create!(:name => 'a')
429
- @b = @a.append_sibling(Label.new(:name => 'b'))
430
- @c = @b.append_sibling(Label.new(:name => 'c'))
431
- end
432
- context "doesn't create sort order gaps" do
433
- it 'from head' do
434
- @a.destroy
435
- expect(@root.reload.children).to eq([@b, @c])
436
- expect(@root.children.map { |ea| ea.order_value }).to eq([0, 1])
437
- end
438
- it 'from mid' do
439
- @b.destroy
440
- expect(@root.reload.children).to eq([@a, @c])
441
- expect(@root.children.map { |ea| ea.order_value }).to eq([0, 1])
442
- end
443
- it 'from tail' do
444
- @c.destroy
445
- expect(@root.reload.children).to eq([@a, @b])
446
- expect(@root.children.map { |ea| ea.order_value }).to eq([0, 1])
447
- end
448
- end
449
-
450
- context 'add_sibling moves descendant nodes' do
451
- let(:roots) { (0..10).map { |ea| Label.create(name: ea) } }
452
- let(:first_root) { roots.first }
453
- let(:last_root) { roots.last }
454
- it 'should retain sort orders of descendants when moving to a new parent' do
455
- expected_order = ('a'..'z').to_a.shuffle
456
- expected_order.map { |ea| first_root.add_child(Label.new(name: ea)) }
457
- actual_order = first_root.children.reload.pluck(:name)
458
- expect(actual_order).to eq(expected_order)
459
- last_root.append_child(first_root)
460
- expect(last_root.self_and_descendants.pluck(:name)).to eq(%w(10 0) + expected_order)
461
- end
462
-
463
- it 'should retain sort orders of descendants when moving within the same new parent' do
464
- path = ('a'..'z').to_a
465
- z = first_root.find_or_create_by_path(path)
466
- z_children_names = (100..150).to_a.shuffle.map { |ea| ea.to_s }
467
- z_children_names.reverse.each { |ea| z.prepend_child(Label.new(name: ea)) }
468
- expect(z.children.reload.pluck(:name)).to eq(z_children_names)
469
- a = first_root.find_by_path(['a'])
470
- # move b up to a's level:
471
- b = a.children.first
472
- a.add_sibling(b)
473
- expect(b.parent).to eq(first_root)
474
- expect(z.children.reload.pluck(:name)).to eq(z_children_names)
475
- end
476
- end
477
-
478
- it "shouldn't fail if all children are destroyed" do
479
- roots = Label.roots.to_a
480
- roots.each { |ea| ea.children.destroy_all }
481
- expect(Label.all.to_a).to match_array(roots)
482
- end
483
- end
484
-
485
- context 'descendent destruction' do
486
- it 'properly destroys descendents created with add_child' do
487
- a = Label.create(name: 'a')
488
- b = Label.new(name: 'b')
489
- a.add_child b
490
- c = Label.new(name: 'c')
491
- b.add_child c
492
- a.destroy
493
- expect(Label.exists?(id: [a.id, b.id, c.id])).to be_falsey
494
- end
495
-
496
- it 'properly destroys descendents created with <<' do
497
- a = Label.create(name: 'a')
498
- b = Label.new(name: 'b')
499
- a.children << b
500
- c = Label.new(name: 'c')
501
- b.children << c
502
- a.destroy
503
- expect(Label.exists?(id: [a.id, b.id, c.id])).to be_falsey
504
- end
505
- end
506
-
507
- context 'preorder' do
508
- it 'returns descendants in proper order' do
509
- create_preorder_tree
510
- a = Label.root
511
- expect(a.name).to eq('a')
512
- expected = ('a'..'r').to_a
513
- expect(a.self_and_descendants_preordered.collect { |ea| ea.name }).to eq(expected)
514
- expect(Label.roots_and_descendants_preordered.collect { |ea| ea.name }).to eq(expected)
515
- # Let's create the second root by hand so we can explicitly set the sort order
516
- Label.create! do |l|
517
- l.name = "a1"
518
- l.order_value = a.order_value + 1
519
- end
520
- create_preorder_tree('1')
521
- # Should be no change:
522
- expect(a.reload.self_and_descendants_preordered.collect { |ea| ea.name }).to eq(expected)
523
- expected += ('a'..'r').collect { |ea| "#{ea}1" }
524
- expect(Label.roots_and_descendants_preordered.collect { |ea| ea.name }).to eq(expected)
525
- end
526
- end unless sqlite? # sqlite doesn't have a power function.
527
-
528
- context 'hash_tree' do
529
- before do
530
- @a = EventLabel.create(name: 'a')
531
- @b = DateLabel.create(name: 'b')
532
- @c = DirectoryLabel.create(name: 'c')
533
- (1..3).each { |i| DirectoryLabel.create!(name: "c#{ i }", mother_id: @c.id) }
534
- end
535
- it 'should return tree with correct scope when called on class' do
536
- tree = DirectoryLabel.hash_tree
537
- expect(tree.keys.size).to eq(1)
538
- expect(tree.keys.first).to eq(@c)
539
- expect(tree[@c].keys.size).to eq(3)
540
- end
541
- it 'should return tree with correct scope when called on all' do
542
- tree = DirectoryLabel.all.hash_tree
543
- expect(tree.keys.size).to eq(1)
544
- expect(tree.keys.first).to eq(@c)
545
- expect(tree[@c].keys.size).to eq(3)
546
- end
547
- it 'should return tree with correct scope when called on scope chain' do
548
- tree = Label.where(name: 'b').hash_tree
549
- expect(tree.keys.size).to eq(1)
550
- expect(tree.keys.first).to eq(@b)
551
- expect(tree[@b]).to eq({})
552
- end
553
- end
554
- end