closure_tree 6.6.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -6
  3. data/Appraisals +59 -1
  4. data/CHANGELOG.md +21 -0
  5. data/Gemfile +0 -12
  6. data/README.md +40 -3
  7. data/closure_tree.gemspec +10 -7
  8. data/lib/closure_tree/finders.rb +15 -5
  9. data/lib/closure_tree/has_closure_tree.rb +2 -0
  10. data/lib/closure_tree/has_closure_tree_root.rb +2 -6
  11. data/lib/closure_tree/hash_tree_support.rb +1 -1
  12. data/lib/closure_tree/hierarchy_maintenance.rb +3 -3
  13. data/lib/closure_tree/model.rb +31 -1
  14. data/lib/closure_tree/numeric_deterministic_ordering.rb +11 -2
  15. data/lib/closure_tree/numeric_order_support.rb +3 -0
  16. data/lib/closure_tree/support.rb +7 -3
  17. data/lib/closure_tree/support_attributes.rb +9 -0
  18. data/lib/closure_tree/support_flags.rb +1 -4
  19. data/lib/closure_tree/version.rb +1 -1
  20. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  21. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  22. metadata +28 -76
  23. data/gemfiles/activerecord_4.2.gemfile +0 -19
  24. data/gemfiles/activerecord_5.0.gemfile +0 -19
  25. data/gemfiles/activerecord_5.1.gemfile +0 -19
  26. data/gemfiles/activerecord_edge.gemfile +0 -20
  27. data/img/example.png +0 -0
  28. data/img/preorder.png +0 -0
  29. data/spec/cache_invalidation_spec.rb +0 -39
  30. data/spec/cuisine_type_spec.rb +0 -38
  31. data/spec/db/database.yml +0 -21
  32. data/spec/db/models.rb +0 -128
  33. data/spec/db/schema.rb +0 -166
  34. data/spec/fixtures/tags.yml +0 -98
  35. data/spec/generators/migration_generator_spec.rb +0 -48
  36. data/spec/has_closure_tree_root_spec.rb +0 -154
  37. data/spec/hierarchy_maintenance_spec.rb +0 -16
  38. data/spec/label_spec.rb +0 -554
  39. data/spec/matcher_spec.rb +0 -34
  40. data/spec/metal_spec.rb +0 -55
  41. data/spec/model_spec.rb +0 -9
  42. data/spec/namespace_type_spec.rb +0 -13
  43. data/spec/parallel_spec.rb +0 -159
  44. data/spec/pool_spec.rb +0 -27
  45. data/spec/spec_helper.rb +0 -24
  46. data/spec/support/database.rb +0 -52
  47. data/spec/support/database_cleaner.rb +0 -14
  48. data/spec/support/exceed_query_limit.rb +0 -18
  49. data/spec/support/hash_monkey_patch.rb +0 -13
  50. data/spec/support/query_counter.rb +0 -18
  51. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  52. data/spec/support_spec.rb +0 -14
  53. data/spec/tag_examples.rb +0 -665
  54. data/spec/tag_spec.rb +0 -6
  55. data/spec/user_spec.rb +0 -174
  56. data/spec/uuid_tag_spec.rb +0 -6
@@ -1,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