closure_tree 6.5.0 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +98 -0
- data/.gitignore +2 -0
- data/.rspec +1 -1
- data/Appraisals +90 -7
- data/CHANGELOG.md +100 -42
- data/Gemfile +3 -11
- data/README.md +68 -24
- data/Rakefile +16 -10
- data/_config.yml +1 -0
- data/bin/appraisal +29 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/closure_tree.gemspec +16 -9
- data/lib/closure_tree/finders.rb +32 -9
- data/lib/closure_tree/has_closure_tree.rb +4 -0
- data/lib/closure_tree/has_closure_tree_root.rb +5 -7
- data/lib/closure_tree/hash_tree_support.rb +4 -4
- data/lib/closure_tree/hierarchy_maintenance.rb +28 -8
- data/lib/closure_tree/model.rb +42 -16
- data/lib/closure_tree/numeric_deterministic_ordering.rb +20 -6
- data/lib/closure_tree/numeric_order_support.rb +7 -3
- data/lib/closure_tree/support.rb +18 -12
- data/lib/closure_tree/support_attributes.rb +10 -1
- data/lib/closure_tree/support_flags.rb +1 -4
- data/lib/closure_tree/version.rb +1 -1
- data/lib/generators/closure_tree/migration_generator.rb +8 -0
- data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
- metadata +78 -79
- data/.travis.yml +0 -29
- data/gemfiles/activerecord_4.2.gemfile +0 -19
- data/gemfiles/activerecord_5.0.gemfile +0 -19
- data/gemfiles/activerecord_5.0_foreigner.gemfile +0 -20
- data/gemfiles/activerecord_edge.gemfile +0 -20
- data/img/example.png +0 -0
- data/img/preorder.png +0 -0
- data/spec/cache_invalidation_spec.rb +0 -39
- data/spec/cuisine_type_spec.rb +0 -38
- data/spec/db/database.yml +0 -21
- data/spec/db/models.rb +0 -128
- data/spec/db/schema.rb +0 -166
- data/spec/fixtures/tags.yml +0 -98
- data/spec/generators/migration_generator_spec.rb +0 -48
- data/spec/has_closure_tree_root_spec.rb +0 -154
- data/spec/hierarchy_maintenance_spec.rb +0 -16
- data/spec/label_spec.rb +0 -554
- data/spec/matcher_spec.rb +0 -34
- data/spec/metal_spec.rb +0 -55
- data/spec/model_spec.rb +0 -9
- data/spec/namespace_type_spec.rb +0 -13
- data/spec/parallel_spec.rb +0 -159
- data/spec/spec_helper.rb +0 -24
- data/spec/support/database.rb +0 -52
- data/spec/support/database_cleaner.rb +0 -14
- data/spec/support/exceed_query_limit.rb +0 -18
- data/spec/support/hash_monkey_patch.rb +0 -13
- data/spec/support/query_counter.rb +0 -18
- data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
- data/spec/support_spec.rb +0 -14
- data/spec/tag_examples.rb +0 -665
- data/spec/tag_spec.rb +0 -6
- data/spec/user_spec.rb +0 -174
- data/spec/uuid_tag_spec.rb +0 -6
data/spec/label_spec.rb
DELETED
@@ -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
|
data/spec/matcher_spec.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe 'ClosureTree::Test::Matcher' do
|
4
|
-
|
5
|
-
describe 'be_a_closure_tree' do
|
6
|
-
it { expect(UUIDTag).to be_a_closure_tree }
|
7
|
-
it { expect(User).to be_a_closure_tree }
|
8
|
-
it { expect(Label).to be_a_closure_tree.ordered }
|
9
|
-
it { expect(Metal).to be_a_closure_tree.ordered(:sort_order) }
|
10
|
-
it { expect(MenuItem).to be_a_closure_tree }
|
11
|
-
it { expect(Contract).not_to be_a_closure_tree }
|
12
|
-
end
|
13
|
-
|
14
|
-
describe 'ordered' do
|
15
|
-
it { expect(Label).to be_a_closure_tree.ordered }
|
16
|
-
it { expect(UUIDTag).to be_a_closure_tree.ordered }
|
17
|
-
it { expect(Metal).to be_a_closure_tree.ordered(:sort_order) }
|
18
|
-
end
|
19
|
-
|
20
|
-
describe 'advisory_lock' do
|
21
|
-
it 'should use advisory lock' do
|
22
|
-
expect(User).to be_a_closure_tree.with_advisory_lock
|
23
|
-
expect(Label).to be_a_closure_tree.ordered.with_advisory_lock
|
24
|
-
expect(Metal).to be_a_closure_tree.ordered(:sort_order).with_advisory_lock
|
25
|
-
end
|
26
|
-
|
27
|
-
describe MenuItem do
|
28
|
-
it 'should not use advisory lock' do
|
29
|
-
is_expected.to be_a_closure_tree.without_advisory_lock
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
data/spec/metal_spec.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Metal do
|
4
|
-
describe '#find_or_create_by_path' do
|
5
|
-
def assert_correctness(grandchild)
|
6
|
-
expect(grandchild).to be_a(Metal)
|
7
|
-
expect(grandchild.description).to eq('slag')
|
8
|
-
child = grandchild.parent
|
9
|
-
expect(child).to be_a(Unobtanium)
|
10
|
-
expect(child.description).to eq('frames')
|
11
|
-
expect(child.value).to eq('child')
|
12
|
-
parent = child.parent
|
13
|
-
expect(parent).to be_a(Adamantium)
|
14
|
-
expect(parent.description).to eq('claws')
|
15
|
-
expect(parent.value).to eq('parent')
|
16
|
-
end
|
17
|
-
|
18
|
-
let(:attr_path) do
|
19
|
-
[
|
20
|
-
{value: 'parent', description: 'claws', metal_type: 'Adamantium'},
|
21
|
-
{value: 'child', description: 'frames', metal_type: 'Unobtanium'},
|
22
|
-
{value: 'grandchild', description: 'slag', metal_type: 'Metal'}
|
23
|
-
]
|
24
|
-
end
|
25
|
-
|
26
|
-
before do
|
27
|
-
# ensure the correct root is used in find_or_create_by_path:
|
28
|
-
[Metal, Adamantium, Unobtanium].each do |metal|
|
29
|
-
metal.find_or_create_by_path(%w(parent child grandchild))
|
30
|
-
end
|
31
|
-
end if false
|
32
|
-
|
33
|
-
it 'creates children from the proper root' do
|
34
|
-
assert_correctness(Metal.find_or_create_by_path(attr_path))
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'supports STI from the base class' do
|
38
|
-
assert_correctness(Metal.find_or_create_by_path(attr_path))
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'supports STI from a subclass' do
|
42
|
-
parent = Adamantium.create!(value: 'parent', description: 'claws')
|
43
|
-
assert_correctness(parent.find_or_create_by_path(attr_path.last(2)))
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'maintains the current STI subclass if attributes are not specified' do
|
47
|
-
leaf = Unobtanium.find_or_create_by_path(%w(a b c d))
|
48
|
-
expect(leaf).to be_a(Unobtanium)
|
49
|
-
expect(leaf.ancestors.map(&:value)).to eq(%w(c b a))
|
50
|
-
leaf.ancestors.each do |anc|
|
51
|
-
expect(anc).to be_a(Unobtanium)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/spec/model_spec.rb
DELETED
data/spec/namespace_type_spec.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Namespace::Type do
|
4
|
-
|
5
|
-
context "class injection" do
|
6
|
-
it "should build hierarchy classname correctly" do
|
7
|
-
expect(Namespace::Type.hierarchy_class.to_s).to eq("Namespace::TypeHierarchy")
|
8
|
-
expect(Namespace::Type._ct.hierarchy_class_name).to eq("Namespace::TypeHierarchy")
|
9
|
-
expect(Namespace::Type._ct.short_hierarchy_class_name).to eq("TypeHierarchy")
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
end
|