closure_tree 4.6.2 → 4.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +6 -1
- data/closure_tree.gemspec +1 -1
- data/gemfiles/activerecord_3.2.gemfile +1 -1
- data/gemfiles/activerecord_4.0.gemfile +1 -1
- data/gemfiles/activerecord_4.1.gemfile +1 -1
- data/lib/closure_tree/hierarchy_maintenance.rb +1 -1
- data/lib/closure_tree/test/matcher.rb +13 -9
- data/lib/closure_tree/version.rb +1 -1
- data/spec/cuisine_type_spec.rb +6 -6
- data/spec/label_spec.rb +122 -121
- data/spec/matcher_spec.rb +16 -14
- data/spec/metal_spec.rb +1 -1
- data/spec/namespace_type_spec.rb +3 -3
- data/spec/parallel_spec.rb +12 -12
- data/spec/support_spec.rb +2 -2
- data/spec/tag_examples.rb +135 -132
- data/spec/user_spec.rb +46 -46
- metadata +6 -6
data/spec/matcher_spec.rb
CHANGED
@@ -3,29 +3,31 @@ require 'spec_helper'
|
|
3
3
|
describe 'ClosureTree::Test::Matcher' do
|
4
4
|
|
5
5
|
describe 'be_a_closure_tree' do
|
6
|
-
it { UUIDTag.
|
7
|
-
it { User.
|
8
|
-
it { Label.
|
9
|
-
it { Metal.
|
10
|
-
it { MenuItem.
|
11
|
-
it { Contract.
|
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
12
|
end
|
13
13
|
|
14
14
|
describe 'ordered' do
|
15
|
-
it { Label.
|
16
|
-
it { UUIDTag.
|
17
|
-
it { Metal.
|
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
18
|
end
|
19
19
|
|
20
20
|
describe 'advisory_lock' do
|
21
21
|
it 'should use advisory lock' do
|
22
|
-
User.
|
23
|
-
Label.
|
24
|
-
Metal.
|
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
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
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
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
data/spec/metal_spec.rb
CHANGED
data/spec/namespace_type_spec.rb
CHANGED
@@ -4,9 +4,9 @@ describe Namespace::Type do
|
|
4
4
|
|
5
5
|
context "class injection" do
|
6
6
|
it "should build hierarchy classname correctly" do
|
7
|
-
Namespace::Type.hierarchy_class.to_s.
|
8
|
-
Namespace::Type._ct.hierarchy_class_name.
|
9
|
-
Namespace::Type._ct.short_hierarchy_class_name.
|
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
10
|
end
|
11
11
|
end
|
12
12
|
|
data/spec/parallel_spec.rb
CHANGED
@@ -58,28 +58,28 @@ describe 'Concurrent creation', if: support_concurrency do
|
|
58
58
|
|
59
59
|
it 'will not create dupes from class methods' do
|
60
60
|
run_workers
|
61
|
-
Tag.roots.collect { |ea| ea.name }.
|
61
|
+
expect(Tag.roots.collect { |ea| ea.name }).to match_array(@names)
|
62
62
|
# No dupe children:
|
63
63
|
%w(a b c).each do |ea|
|
64
|
-
Tag.where(name: ea).size.
|
64
|
+
expect(Tag.where(name: ea).size).to eq(@iterations)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
it 'will not create dupes from instance methods' do
|
69
69
|
@target = Tag.create!(name: 'root')
|
70
70
|
run_workers
|
71
|
-
@target.reload.children.collect { |ea| ea.name }.
|
72
|
-
Tag.where(name: @names).size.
|
71
|
+
expect(@target.reload.children.collect { |ea| ea.name }).to match_array(@names)
|
72
|
+
expect(Tag.where(name: @names).size).to eq(@iterations)
|
73
73
|
%w(a b c).each do |ea|
|
74
|
-
Tag.where(name: ea).size.
|
74
|
+
expect(Tag.where(name: ea).size).to eq(@iterations)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'creates dupe roots without advisory locks' do
|
79
79
|
# disable with_advisory_lock:
|
80
|
-
Tag.
|
80
|
+
allow(Tag).to receive(:with_advisory_lock) { |_lock_name, &block| block.call }
|
81
81
|
run_workers
|
82
|
-
Tag.where(name: @names).size.
|
82
|
+
expect(Tag.where(name: @names).size).to be > @iterations
|
83
83
|
end
|
84
84
|
|
85
85
|
class SiblingPrependerWorker < WorkerBase
|
@@ -133,8 +133,8 @@ describe 'Concurrent creation', if: support_concurrency do
|
|
133
133
|
creator_threads.each { |ea| ea.join }
|
134
134
|
run_destruction = false
|
135
135
|
destroyer_threads.each { |ea| ea.join }
|
136
|
-
added_children.
|
137
|
-
deleted_children.
|
136
|
+
expect(added_children).to match(expected_children)
|
137
|
+
expect(deleted_children).to match(expected_children)
|
138
138
|
end
|
139
139
|
|
140
140
|
xit 'fails to deadlock while simultaneously deleting items from the same hierarchy' do
|
@@ -149,7 +149,7 @@ describe 'Concurrent creation', if: support_concurrency do
|
|
149
149
|
end
|
150
150
|
end
|
151
151
|
destroyer_threads.each { |ea| ea.join }
|
152
|
-
User.all.
|
152
|
+
expect(User.all).to be_empty
|
153
153
|
end
|
154
154
|
|
155
155
|
class SiblingPrependerWorker < WorkerBase
|
@@ -168,10 +168,10 @@ describe 'Concurrent creation', if: support_concurrency do
|
|
168
168
|
run_workers(SiblingPrependerWorker)
|
169
169
|
children = Label.roots
|
170
170
|
uniq_order_values = children.collect { |ea| ea.order_value }.uniq
|
171
|
-
children.size.
|
171
|
+
expect(children.size).to eq(uniq_order_values.size)
|
172
172
|
|
173
173
|
# The only non-root node should be "root":
|
174
|
-
Label.all.select { |ea| ea.root? }.
|
174
|
+
expect(Label.all.select { |ea| ea.root? }).to eq([@target.parent])
|
175
175
|
end
|
176
176
|
|
177
177
|
end
|
data/spec/support_spec.rb
CHANGED
@@ -4,11 +4,11 @@ describe ClosureTree::Support do
|
|
4
4
|
let(:sut) { Tag._ct }
|
5
5
|
it 'passes through table names without prefix and suffix' do
|
6
6
|
expected = 'some_random_table_name'
|
7
|
-
sut.remove_prefix_and_suffix(expected).
|
7
|
+
expect(sut.remove_prefix_and_suffix(expected)).to eq(expected)
|
8
8
|
end
|
9
9
|
it 'extracts through table name with prefix and suffix' do
|
10
10
|
expected = 'some_random_table_name'
|
11
11
|
tn = ActiveRecord::Base.table_name_prefix + expected + ActiveRecord::Base.table_name_suffix
|
12
|
-
sut.remove_prefix_and_suffix(tn).
|
12
|
+
expect(sut.remove_prefix_and_suffix(tn)).to eq(expected)
|
13
13
|
end
|
14
14
|
end
|
data/spec/tag_examples.rb
CHANGED
@@ -9,19 +9,19 @@ shared_examples_for Tag do
|
|
9
9
|
|
10
10
|
it 'has correct accessible_attributes' do
|
11
11
|
if tag_class._ct.use_attr_accessible?
|
12
|
-
tag_class.accessible_attributes.to_a.
|
12
|
+
expect(tag_class.accessible_attributes.to_a).to match_array(%w(parent name title))
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'should build hierarchy classname correctly' do
|
17
|
-
tag_class.hierarchy_class.
|
18
|
-
tag_class._ct.hierarchy_class_name.
|
19
|
-
tag_class._ct.short_hierarchy_class_name.
|
17
|
+
expect(tag_class.hierarchy_class).to eq(tag_hierarchy_class)
|
18
|
+
expect(tag_class._ct.hierarchy_class_name).to eq(tag_hierarchy_class.to_s)
|
19
|
+
expect(tag_class._ct.short_hierarchy_class_name).to eq(tag_hierarchy_class.to_s)
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'should have a correct parent column name' do
|
23
23
|
expected_parent_column_name = tag_class == UUIDTag ? "parent_uuid" : "parent_id"
|
24
|
-
tag_class._ct.parent_column_name.
|
24
|
+
expect(tag_class._ct.parent_column_name).to eq(expected_parent_column_name)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -29,13 +29,13 @@ shared_examples_for Tag do
|
|
29
29
|
|
30
30
|
context "with no tags" do
|
31
31
|
it "should return no entities" do
|
32
|
-
tag_class.roots.
|
33
|
-
tag_class.leaves.
|
32
|
+
expect(tag_class.roots).to be_empty
|
33
|
+
expect(tag_class.leaves).to be_empty
|
34
34
|
end
|
35
35
|
|
36
36
|
it "#find_or_create_by_path" do
|
37
37
|
a = tag_class.create!(:name => 'a')
|
38
|
-
a.find_or_create_by_path(%w{b c}).ancestry_path.
|
38
|
+
expect(a.find_or_create_by_path(%w{b c}).ancestry_path).to eq(%w{a b c})
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -45,21 +45,21 @@ shared_examples_for Tag do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it "should be a leaf" do
|
48
|
-
@tag.leaf
|
48
|
+
expect(@tag.leaf?).to be_truthy
|
49
49
|
end
|
50
50
|
|
51
51
|
it "should be a root" do
|
52
|
-
@tag.root
|
52
|
+
expect(@tag.root?).to be_truthy
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'has no parent' do
|
56
|
-
@tag.parent.
|
56
|
+
expect(@tag.parent).to be_nil
|
57
57
|
end
|
58
58
|
|
59
59
|
it "should return the only entity as a root and leaf" do
|
60
|
-
tag_class.all.
|
61
|
-
tag_class.roots.
|
62
|
-
tag_class.leaves.
|
60
|
+
expect(tag_class.all).to eq([@tag])
|
61
|
+
expect(tag_class.roots).to eq([@tag])
|
62
|
+
expect(tag_class.leaves).to eq([@tag])
|
63
63
|
end
|
64
64
|
|
65
65
|
context "with child" do
|
@@ -68,16 +68,16 @@ shared_examples_for Tag do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def assert_roots_and_leaves
|
71
|
-
@tag.root
|
72
|
-
@tag.leaf
|
71
|
+
expect(@tag.root?).to be_truthy
|
72
|
+
expect(@tag.leaf?).to be_falsey
|
73
73
|
|
74
|
-
@child.root
|
75
|
-
@child.leaf
|
74
|
+
expect(@child.root?).to be_falsey
|
75
|
+
expect(@child.leaf?).to be_truthy
|
76
76
|
end
|
77
77
|
|
78
78
|
def assert_parent_and_children
|
79
|
-
@child.reload.parent.
|
80
|
-
@tag.reload.children.to_a.
|
79
|
+
expect(@child.reload.parent).to eq(@tag)
|
80
|
+
expect(@tag.reload.children.to_a).to eq([@child])
|
81
81
|
end
|
82
82
|
|
83
83
|
it "adds children through add_child" do
|
@@ -100,15 +100,15 @@ shared_examples_for Tag do
|
|
100
100
|
@leaf = @root.add_child(tag_class.create!(:name => "leaf"))
|
101
101
|
end
|
102
102
|
it "should return a simple root and leaf" do
|
103
|
-
tag_class.roots.
|
104
|
-
tag_class.leaves.
|
103
|
+
expect(tag_class.roots).to eq([@root])
|
104
|
+
expect(tag_class.leaves).to eq([@leaf])
|
105
105
|
end
|
106
106
|
it "should return child_ids for root" do
|
107
|
-
@root.child_ids.
|
107
|
+
expect(@root.child_ids).to eq([@leaf.id])
|
108
108
|
end
|
109
109
|
|
110
110
|
it "should return an empty array for leaves" do
|
111
|
-
@leaf.child_ids.
|
111
|
+
expect(@leaf.child_ids).to be_empty
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
@@ -121,55 +121,55 @@ shared_examples_for Tag do
|
|
121
121
|
end
|
122
122
|
|
123
123
|
it "should create all tags" do
|
124
|
-
tag_class.all.to_a.
|
124
|
+
expect(tag_class.all.to_a).to match_array([@root, @mid, @leaf])
|
125
125
|
end
|
126
126
|
|
127
127
|
it "should return a root and leaf without middle tag" do
|
128
|
-
tag_class.roots.
|
129
|
-
tag_class.leaves.
|
128
|
+
expect(tag_class.roots).to eq([@root])
|
129
|
+
expect(tag_class.leaves).to eq([@leaf])
|
130
130
|
end
|
131
131
|
|
132
132
|
it "should delete leaves" do
|
133
133
|
tag_class.leaves.destroy_all
|
134
|
-
tag_class.roots.
|
135
|
-
tag_class.leaves.
|
134
|
+
expect(tag_class.roots).to eq([@root]) # untouched
|
135
|
+
expect(tag_class.leaves).to eq([@mid])
|
136
136
|
end
|
137
137
|
|
138
138
|
it "should delete everything if you delete the roots" do
|
139
139
|
tag_class.roots.destroy_all
|
140
|
-
tag_class.all.
|
141
|
-
tag_class.roots.
|
142
|
-
tag_class.leaves.
|
143
|
-
DestroyedTag.all.map { |t| t.name }.
|
140
|
+
expect(tag_class.all).to be_empty
|
141
|
+
expect(tag_class.roots).to be_empty
|
142
|
+
expect(tag_class.leaves).to be_empty
|
143
|
+
expect(DestroyedTag.all.map { |t| t.name }).to match_array(%w{root mid leaf})
|
144
144
|
end
|
145
145
|
|
146
146
|
it 'fix self_and_ancestors properly on reparenting' do
|
147
147
|
t = tag_class.create! :name => 'moar leaf'
|
148
|
-
t.self_and_ancestors.to_a.
|
148
|
+
expect(t.self_and_ancestors.to_a).to eq([t])
|
149
149
|
@mid.children << t
|
150
|
-
t.self_and_ancestors.to_a.
|
150
|
+
expect(t.self_and_ancestors.to_a).to eq([t, @mid, @root])
|
151
151
|
end
|
152
152
|
|
153
153
|
it 'prevents ancestor loops' do
|
154
154
|
@leaf.add_child @root
|
155
|
-
@root.
|
156
|
-
@root.reload.descendants.
|
155
|
+
expect(@root).not_to be_valid
|
156
|
+
expect(@root.reload.descendants).to include(@leaf)
|
157
157
|
end
|
158
158
|
|
159
159
|
it 'moves non-leaves' do
|
160
160
|
new_root = tag_class.create! :name => "new_root"
|
161
161
|
new_root.children << @mid
|
162
|
-
@root.reload.descendants.
|
163
|
-
new_root.descendants.
|
164
|
-
@leaf.reload.ancestry_path.
|
162
|
+
expect(@root.reload.descendants).to be_empty
|
163
|
+
expect(new_root.descendants).to eq([@mid, @leaf])
|
164
|
+
expect(@leaf.reload.ancestry_path).to eq(%w{new_root mid leaf})
|
165
165
|
end
|
166
166
|
|
167
167
|
it 'moves leaves' do
|
168
168
|
new_root = tag_class.create! :name => "new_root"
|
169
169
|
new_root.children << @leaf
|
170
|
-
new_root.descendants.
|
171
|
-
@root.reload.descendants.
|
172
|
-
@leaf.reload.ancestry_path.
|
170
|
+
expect(new_root.descendants).to eq([@leaf])
|
171
|
+
expect(@root.reload.descendants).to eq([@mid])
|
172
|
+
expect(@leaf.reload.ancestry_path).to eq(%w{new_root leaf})
|
173
173
|
end
|
174
174
|
end
|
175
175
|
|
@@ -181,55 +181,55 @@ shared_examples_for Tag do
|
|
181
181
|
end
|
182
182
|
|
183
183
|
it "should create all tags" do
|
184
|
-
tag_class.all.to_a.
|
184
|
+
expect(tag_class.all.to_a).to match_array([@root, @mid, @leaf])
|
185
185
|
end
|
186
186
|
|
187
187
|
it "should return a root and leaf without middle tag" do
|
188
|
-
tag_class.roots.
|
189
|
-
tag_class.leaves.
|
188
|
+
expect(tag_class.roots).to eq([@root])
|
189
|
+
expect(tag_class.leaves).to eq([@leaf])
|
190
190
|
end
|
191
191
|
|
192
192
|
it "should prevent parental loops from torso" do
|
193
193
|
@mid.children << @root
|
194
|
-
@root.valid
|
195
|
-
@mid.reload.children.
|
194
|
+
expect(@root.valid?).to be_falsey
|
195
|
+
expect(@mid.reload.children).to eq([@leaf])
|
196
196
|
end
|
197
197
|
|
198
198
|
it "should prevent parental loops from toes" do
|
199
199
|
@leaf.children << @root
|
200
|
-
@root.valid
|
201
|
-
@leaf.reload.children.
|
200
|
+
expect(@root.valid?).to be_falsey
|
201
|
+
expect(@leaf.reload.children).to be_empty
|
202
202
|
end
|
203
203
|
|
204
204
|
it "should support re-parenting" do
|
205
205
|
@root.children << @leaf
|
206
|
-
tag_class.leaves.
|
206
|
+
expect(tag_class.leaves).to eq([@leaf, @mid])
|
207
207
|
end
|
208
208
|
|
209
209
|
it "cleans up hierarchy references for leaves" do
|
210
210
|
@leaf.destroy
|
211
|
-
tag_hierarchy_class.where(:ancestor_id => @leaf.id).
|
212
|
-
tag_hierarchy_class.where(:descendant_id => @leaf.id).
|
211
|
+
expect(tag_hierarchy_class.where(:ancestor_id => @leaf.id)).to be_empty
|
212
|
+
expect(tag_hierarchy_class.where(:descendant_id => @leaf.id)).to be_empty
|
213
213
|
end
|
214
214
|
|
215
215
|
it "cleans up hierarchy references" do
|
216
216
|
@mid.destroy
|
217
|
-
tag_hierarchy_class.where(:ancestor_id => @mid.id).
|
218
|
-
tag_hierarchy_class.where(:descendant_id => @mid.id).
|
219
|
-
@root.reload.
|
217
|
+
expect(tag_hierarchy_class.where(:ancestor_id => @mid.id)).to be_empty
|
218
|
+
expect(tag_hierarchy_class.where(:descendant_id => @mid.id)).to be_empty
|
219
|
+
expect(@root.reload).to be_root
|
220
220
|
root_hiers = @root.ancestor_hierarchies.to_a
|
221
|
-
root_hiers.size.
|
222
|
-
tag_hierarchy_class.where(:ancestor_id => @root.id).
|
223
|
-
tag_hierarchy_class.where(:descendant_id => @root.id).
|
221
|
+
expect(root_hiers.size).to eq(1)
|
222
|
+
expect(tag_hierarchy_class.where(:ancestor_id => @root.id)).to eq(root_hiers)
|
223
|
+
expect(tag_hierarchy_class.where(:descendant_id => @root.id)).to eq(root_hiers)
|
224
224
|
end
|
225
225
|
|
226
226
|
it "should have different hash codes for each hierarchy model" do
|
227
227
|
hashes = tag_hierarchy_class.all.map(&:hash)
|
228
|
-
hashes.
|
228
|
+
expect(hashes).to match_array(hashes.uniq)
|
229
229
|
end
|
230
230
|
|
231
231
|
it "should return the same hash code for equal hierarchy models" do
|
232
|
-
tag_hierarchy_class.first.hash.
|
232
|
+
expect(tag_hierarchy_class.first.hash).to eq(tag_hierarchy_class.first.hash)
|
233
233
|
end
|
234
234
|
end
|
235
235
|
|
@@ -241,23 +241,26 @@ shared_examples_for Tag do
|
|
241
241
|
parent.children << child2
|
242
242
|
child3 = tag_class.new(:name => 'Third Child')
|
243
243
|
parent.add_child child3
|
244
|
-
grandparent.self_and_descendants.collect(&:name).
|
244
|
+
expect(grandparent.self_and_descendants.collect(&:name)).to eq(
|
245
245
|
["Grandparent", "Parent", "First Child", "Second Child", "Third Child"]
|
246
|
-
|
246
|
+
)
|
247
|
+
expect(child1.ancestry_path).to eq(
|
247
248
|
["Grandparent", "Parent", "First Child"]
|
248
|
-
|
249
|
+
)
|
250
|
+
expect(child3.ancestry_path).to eq(
|
249
251
|
["Grandparent", "Parent", "Third Child"]
|
252
|
+
)
|
250
253
|
d = tag_class.find_or_create_by_path %w(a b c d)
|
251
254
|
h = tag_class.find_or_create_by_path %w(e f g h)
|
252
255
|
e = h.root
|
253
256
|
d.add_child(e) # "d.children << e" would work too, of course
|
254
|
-
h.ancestry_path.
|
257
|
+
expect(h.ancestry_path).to eq(%w(a b c d e f g h))
|
255
258
|
end
|
256
259
|
|
257
260
|
it "roots sort alphabetically" do
|
258
261
|
expected = ("a".."z").to_a
|
259
262
|
expected.shuffle.each { |ea| tag_class.create!(:name => ea) }
|
260
|
-
tag_class.roots.collect { |ea| ea.name }.
|
263
|
+
expect(tag_class.roots.collect { |ea| ea.name }).to eq(expected)
|
261
264
|
end
|
262
265
|
|
263
266
|
context "with simple tree" do
|
@@ -279,32 +282,32 @@ shared_examples_for Tag do
|
|
279
282
|
end
|
280
283
|
|
281
284
|
it 'should find global roots' do
|
282
|
-
tag_class.roots.to_a.
|
285
|
+
expect(tag_class.roots.to_a).to match_array(@expected_roots)
|
283
286
|
end
|
284
287
|
it 'should return root? for roots' do
|
285
|
-
@expected_roots.each { |ea| ea.
|
288
|
+
@expected_roots.each { |ea| expect(ea).to be_root }
|
286
289
|
end
|
287
290
|
it 'should not return root? for non-roots' do
|
288
|
-
[@b1, @b2, @c1a, @c1b].each { |ea| ea.
|
291
|
+
[@b1, @b2, @c1a, @c1b].each { |ea| expect(ea).not_to be_root }
|
289
292
|
end
|
290
293
|
it 'should return the correct root' do
|
291
294
|
{@a1 => @a1, @a2 => @a2, @a3 => @a3,
|
292
295
|
@b1 => @a1, @b2 => @a2, @c1a => @a1, @c1b => @a1}.each do |node, root|
|
293
|
-
node.root.
|
296
|
+
expect(node.root).to eq(root)
|
294
297
|
end
|
295
298
|
end
|
296
299
|
it 'should assemble global leaves' do
|
297
|
-
tag_class.leaves.to_a.
|
300
|
+
expect(tag_class.leaves.to_a).to match_array(@expected_leaves)
|
298
301
|
end
|
299
302
|
it 'assembles siblings properly' do
|
300
303
|
@expected_siblings.each do |siblings|
|
301
304
|
siblings.each do |ea|
|
302
|
-
ea.self_and_siblings.to_a.
|
303
|
-
ea.siblings.to_a.
|
305
|
+
expect(ea.self_and_siblings.to_a).to match_array(siblings)
|
306
|
+
expect(ea.siblings.to_a).to match_array(siblings - [ea])
|
304
307
|
end
|
305
308
|
end
|
306
309
|
@expected_only_children.each do |ea|
|
307
|
-
ea.siblings.
|
310
|
+
expect(ea.siblings).to eq([])
|
308
311
|
end
|
309
312
|
end
|
310
313
|
it 'assembles before_siblings' do
|
@@ -312,7 +315,7 @@ shared_examples_for Tag do
|
|
312
315
|
(siblings.size - 1).times do |i|
|
313
316
|
target = siblings[i]
|
314
317
|
expected_before = siblings.first(i)
|
315
|
-
target.siblings_before.to_a.
|
318
|
+
expect(target.siblings_before.to_a).to eq(expected_before)
|
316
319
|
end
|
317
320
|
end
|
318
321
|
end
|
@@ -321,49 +324,49 @@ shared_examples_for Tag do
|
|
321
324
|
(siblings.size - 1).times do |i|
|
322
325
|
target = siblings[i]
|
323
326
|
expected_after = siblings.last(siblings.size - 1 - i)
|
324
|
-
target.siblings_after.to_a.
|
327
|
+
expect(target.siblings_after.to_a).to eq(expected_after)
|
325
328
|
end
|
326
329
|
end
|
327
330
|
end
|
328
331
|
it 'should assemble instance leaves' do
|
329
332
|
{@a1 => [@b1b, @c1a, @c1b, @c1c], @b1 => [@c1a, @c1b, @c1c], @a2 => [@b2]}.each do |node, leaves|
|
330
|
-
node.leaves.to_a.
|
333
|
+
expect(node.leaves.to_a).to eq(leaves)
|
331
334
|
end
|
332
|
-
@expected_leaves.each { |ea| ea.leaves.to_a.
|
335
|
+
@expected_leaves.each { |ea| expect(ea.leaves.to_a).to eq([ea]) }
|
333
336
|
end
|
334
337
|
it 'should return leaf? for leaves' do
|
335
|
-
@expected_leaves.each { |ea| ea.
|
338
|
+
@expected_leaves.each { |ea| expect(ea).to be_leaf }
|
336
339
|
end
|
337
340
|
|
338
341
|
it 'can move roots' do
|
339
342
|
@c1a.children << @a2
|
340
343
|
@b2.reload.children << @a3
|
341
|
-
@a3.reload.ancestry_path.
|
344
|
+
expect(@a3.reload.ancestry_path).to eq(%w(a1 b1 c1a a2 b2 a3))
|
342
345
|
end
|
343
346
|
|
344
347
|
it 'cascade-deletes from roots' do
|
345
348
|
victim_names = @a1.self_and_descendants.map(&:name)
|
346
349
|
survivor_names = tag_class.all.map(&:name) - victim_names
|
347
350
|
@a1.destroy
|
348
|
-
tag_class.all.map(&:name).
|
351
|
+
expect(tag_class.all.map(&:name)).to eq(survivor_names)
|
349
352
|
end
|
350
353
|
end
|
351
354
|
|
352
355
|
context 'with_ancestor' do
|
353
356
|
it 'works with no rows' do
|
354
|
-
tag_class.with_ancestor.to_a.
|
357
|
+
expect(tag_class.with_ancestor.to_a).to be_empty
|
355
358
|
end
|
356
359
|
it 'finds only children' do
|
357
360
|
c = tag_class.find_or_create_by_path %w(A B C)
|
358
361
|
a, b = c.parent.parent, c.parent
|
359
362
|
spurious_tags = tag_class.find_or_create_by_path %w(D E)
|
360
|
-
tag_class.with_ancestor(a).to_a.
|
363
|
+
expect(tag_class.with_ancestor(a).to_a).to eq([b, c])
|
361
364
|
end
|
362
365
|
it 'limits subsequent where clauses' do
|
363
366
|
a1c = tag_class.find_or_create_by_path %w(A1 B C)
|
364
367
|
a2c = tag_class.find_or_create_by_path %w(A2 B C)
|
365
|
-
tag_class.where(:name => "C").to_a.
|
366
|
-
tag_class.with_ancestor(a1c.parent.parent).where(:name => "C").to_a.
|
368
|
+
expect(tag_class.where(:name => "C").to_a).to match_array([a1c, a2c])
|
369
|
+
expect(tag_class.with_ancestor(a1c.parent.parent).where(:name => "C").to_a).to eq([a1c])
|
367
370
|
end
|
368
371
|
end
|
369
372
|
|
@@ -379,65 +382,65 @@ shared_examples_for Tag do
|
|
379
382
|
end
|
380
383
|
|
381
384
|
it "should build ancestry path" do
|
382
|
-
@child.ancestry_path.
|
383
|
-
@child.ancestry_path(:name).
|
384
|
-
@child.ancestry_path(:title).
|
385
|
+
expect(@child.ancestry_path).to eq(%w{grandparent parent child})
|
386
|
+
expect(@child.ancestry_path(:name)).to eq(%w{grandparent parent child})
|
387
|
+
expect(@child.ancestry_path(:title)).to eq(%w{Nonnie Mom Kid})
|
385
388
|
end
|
386
389
|
|
387
390
|
it 'assembles ancestors' do
|
388
|
-
@child.ancestors.
|
389
|
-
@child.self_and_ancestors.
|
391
|
+
expect(@child.ancestors).to eq([@parent, @grandparent])
|
392
|
+
expect(@child.self_and_ancestors).to eq([@child, @parent, @grandparent])
|
390
393
|
end
|
391
394
|
|
392
395
|
it "should find by path" do
|
393
396
|
# class method:
|
394
|
-
tag_class.find_by_path(%w{grandparent parent child}).
|
397
|
+
expect(tag_class.find_by_path(%w{grandparent parent child})).to eq(@child)
|
395
398
|
# instance method:
|
396
|
-
@parent.find_by_path(%w{child}).
|
397
|
-
@grandparent.find_by_path(%w{parent child}).
|
398
|
-
@parent.find_by_path(%w{child larvae}).
|
399
|
+
expect(@parent.find_by_path(%w{child})).to eq(@child)
|
400
|
+
expect(@grandparent.find_by_path(%w{parent child})).to eq(@child)
|
401
|
+
expect(@parent.find_by_path(%w{child larvae})).to be_nil
|
399
402
|
end
|
400
403
|
|
401
404
|
it "finds correctly rooted paths" do
|
402
405
|
decoy = tag_class.find_or_create_by_path %w(a b c d)
|
403
406
|
b_d = tag_class.find_or_create_by_path %w(b c d)
|
404
|
-
tag_class.find_by_path(%w(b c d)).
|
405
|
-
tag_class.find_by_path(%w(c d)).
|
407
|
+
expect(tag_class.find_by_path(%w(b c d))).to eq(b_d)
|
408
|
+
expect(tag_class.find_by_path(%w(c d))).to be_nil
|
406
409
|
end
|
407
410
|
|
408
411
|
it "find_by_path for 1 node" do
|
409
412
|
b = tag_class.find_or_create_by_path %w(a b)
|
410
413
|
b2 = b.root.find_by_path(%w(b))
|
411
|
-
b2.
|
414
|
+
expect(b2).to eq(b)
|
412
415
|
end
|
413
416
|
|
414
417
|
it "find_by_path for 2 nodes" do
|
415
418
|
c = tag_class.find_or_create_by_path %w(a b c)
|
416
|
-
c.root.find_by_path(%w(b c)).
|
417
|
-
c.root.find_by_path(%w(a c)).
|
418
|
-
c.root.find_by_path(%w(c)).
|
419
|
+
expect(c.root.find_by_path(%w(b c))).to eq(c)
|
420
|
+
expect(c.root.find_by_path(%w(a c))).to be_nil
|
421
|
+
expect(c.root.find_by_path(%w(c))).to be_nil
|
419
422
|
end
|
420
423
|
|
421
424
|
it "find_by_path for 3 nodes" do
|
422
425
|
d = tag_class.find_or_create_by_path %w(a b c d)
|
423
|
-
d.root.find_by_path(%w(b c d)).
|
424
|
-
tag_class.find_by_path(%w(a b c d)).
|
425
|
-
tag_class.find_by_path(%w(d)).
|
426
|
+
expect(d.root.find_by_path(%w(b c d))).to eq(d)
|
427
|
+
expect(tag_class.find_by_path(%w(a b c d))).to eq(d)
|
428
|
+
expect(tag_class.find_by_path(%w(d))).to be_nil
|
426
429
|
end
|
427
430
|
|
428
431
|
it "should return nil for missing nodes" do
|
429
|
-
tag_class.find_by_path(%w{missing}).
|
430
|
-
tag_class.find_by_path(%w{grandparent missing}).
|
431
|
-
tag_class.find_by_path(%w{grandparent parent missing}).
|
432
|
-
tag_class.find_by_path(%w{grandparent parent missing child}).
|
432
|
+
expect(tag_class.find_by_path(%w{missing})).to be_nil
|
433
|
+
expect(tag_class.find_by_path(%w{grandparent missing})).to be_nil
|
434
|
+
expect(tag_class.find_by_path(%w{grandparent parent missing})).to be_nil
|
435
|
+
expect(tag_class.find_by_path(%w{grandparent parent missing child})).to be_nil
|
433
436
|
end
|
434
437
|
|
435
438
|
it ".find_or_create_by_path" do
|
436
439
|
grandparent = tag_class.find_or_create_by_path(%w{grandparent})
|
437
|
-
grandparent.
|
440
|
+
expect(grandparent).to eq(@grandparent)
|
438
441
|
child = tag_class.find_or_create_by_path(%w{grandparent parent child})
|
439
|
-
child.
|
440
|
-
tag_class.find_or_create_by_path(%w{events anniversary}).ancestry_path.
|
442
|
+
expect(child).to eq(@child)
|
443
|
+
expect(tag_class.find_or_create_by_path(%w{events anniversary}).ancestry_path).to eq(%w{events anniversary})
|
441
444
|
end
|
442
445
|
|
443
446
|
it "should respect attribute hashes with both selection and creation" do
|
@@ -445,18 +448,18 @@ shared_examples_for Tag do
|
|
445
448
|
attrs = {:title => expected_title}
|
446
449
|
existing_title = @grandparent.title
|
447
450
|
new_grandparent = tag_class.find_or_create_by_path(%w{grandparent}, attrs)
|
448
|
-
new_grandparent.
|
449
|
-
new_grandparent.title.
|
450
|
-
@grandparent.reload.title.
|
451
|
+
expect(new_grandparent).not_to eq(@grandparent)
|
452
|
+
expect(new_grandparent.title).to eq(expected_title)
|
453
|
+
expect(@grandparent.reload.title).to eq(existing_title)
|
451
454
|
end
|
452
455
|
|
453
456
|
it "should create a hierarchy with a given attribute" do
|
454
457
|
expected_title = 'unicorn rainbows'
|
455
458
|
attrs = {:title => expected_title}
|
456
459
|
child = tag_class.find_or_create_by_path(%w{grandparent parent child}, attrs)
|
457
|
-
child.
|
460
|
+
expect(child).not_to eq(@child)
|
458
461
|
[child, child.parent, child.parent.parent].each do |ea|
|
459
|
-
ea.title.
|
462
|
+
expect(ea.title).to eq(expected_title)
|
460
463
|
end
|
461
464
|
end
|
462
465
|
end
|
@@ -477,22 +480,22 @@ shared_examples_for Tag do
|
|
477
480
|
|
478
481
|
context "#hash_tree" do
|
479
482
|
it "returns {} for depth 0" do
|
480
|
-
tag_class.hash_tree(:limit_depth => 0).
|
483
|
+
expect(tag_class.hash_tree(:limit_depth => 0)).to eq({})
|
481
484
|
end
|
482
485
|
it "limit_depth 1" do
|
483
|
-
tag_class.hash_tree(:limit_depth => 1).
|
486
|
+
expect(tag_class.hash_tree(:limit_depth => 1)).to eq({@a => {}})
|
484
487
|
end
|
485
488
|
it "limit_depth 2" do
|
486
|
-
tag_class.hash_tree(:limit_depth => 2).
|
489
|
+
expect(tag_class.hash_tree(:limit_depth => 2)).to eq({@a => {@b => {}, @b2 => {}}})
|
487
490
|
end
|
488
491
|
it "limit_depth 3" do
|
489
|
-
tag_class.hash_tree(:limit_depth => 3).
|
492
|
+
expect(tag_class.hash_tree(:limit_depth => 3)).to eq({@a => {@b => {@c1 => {}, @c2 => {}}, @b2 => {}}})
|
490
493
|
end
|
491
494
|
it "limit_depth 4" do
|
492
|
-
tag_class.hash_tree(:limit_depth => 4).
|
495
|
+
expect(tag_class.hash_tree(:limit_depth => 4)).to eq(@full_tree)
|
493
496
|
end
|
494
497
|
it "no limit holdum" do
|
495
|
-
tag_class.hash_tree.
|
498
|
+
expect(tag_class.hash_tree).to eq(@full_tree)
|
496
499
|
end
|
497
500
|
end
|
498
501
|
|
@@ -500,7 +503,7 @@ shared_examples_for Tag do
|
|
500
503
|
# the named scope is complicated enough that an incorrect join could result in unnecessarily
|
501
504
|
# duplicated rows:
|
502
505
|
a = scope.collect { |ea| ea.id }
|
503
|
-
a.
|
506
|
+
expect(a).to eq(a.uniq)
|
504
507
|
end
|
505
508
|
|
506
509
|
context "#hash_tree_scope" do
|
@@ -529,25 +532,25 @@ shared_examples_for Tag do
|
|
529
532
|
before :each do
|
530
533
|
end
|
531
534
|
it "returns {} for depth 0" do
|
532
|
-
@b.hash_tree(:limit_depth => 0).
|
535
|
+
expect(@b.hash_tree(:limit_depth => 0)).to eq({})
|
533
536
|
end
|
534
537
|
it "limit_depth 1" do
|
535
|
-
@b.hash_tree(:limit_depth => 1).
|
538
|
+
expect(@b.hash_tree(:limit_depth => 1)).to eq({@b => {}})
|
536
539
|
end
|
537
540
|
it "limit_depth 2" do
|
538
|
-
@b.hash_tree(:limit_depth => 2).
|
541
|
+
expect(@b.hash_tree(:limit_depth => 2)).to eq({@b => {@c1 => {}, @c2 => {}}})
|
539
542
|
end
|
540
543
|
it "limit_depth 3" do
|
541
|
-
@b.hash_tree(:limit_depth => 3).
|
544
|
+
expect(@b.hash_tree(:limit_depth => 3)).to eq({@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}})
|
542
545
|
end
|
543
546
|
it "no limit holdum from subsubroot" do
|
544
|
-
@c1.hash_tree.
|
547
|
+
expect(@c1.hash_tree).to eq({@c1 => {@d1 => {}}})
|
545
548
|
end
|
546
549
|
it "no limit holdum from subroot" do
|
547
|
-
@b.hash_tree.
|
550
|
+
expect(@b.hash_tree).to eq({@b => {@c1 => {@d1 => {}}, @c2 => {@d2 => {}}}})
|
548
551
|
end
|
549
552
|
it "no limit holdum from root" do
|
550
|
-
@a.hash_tree.
|
553
|
+
expect(@a.hash_tree).to eq(@full_tree)
|
551
554
|
end
|
552
555
|
end
|
553
556
|
end
|
@@ -556,13 +559,13 @@ shared_examples_for Tag do
|
|
556
559
|
it 'should find_or_create very deep nodes' do
|
557
560
|
expected_ancestry_path = (1..200).to_a.map { |ea| ea.to_s }
|
558
561
|
target = tag_class.find_or_create_by_path(expected_ancestry_path)
|
559
|
-
target.ancestry_path.
|
562
|
+
expect(target.ancestry_path).to eq(expected_ancestry_path)
|
560
563
|
end
|
561
564
|
end
|
562
565
|
|
563
566
|
describe 'DOT rendering' do
|
564
567
|
it 'should render for an empty scope' do
|
565
|
-
tag_class.to_dot_digraph(tag_class.where("0=1")).
|
568
|
+
expect(tag_class.to_dot_digraph(tag_class.where("0=1"))).to eq("digraph G {\n}\n")
|
566
569
|
end
|
567
570
|
it 'should render for an empty scope' do
|
568
571
|
tag_class.find_or_create_by_path(%w(a b1 c1))
|
@@ -570,7 +573,7 @@ shared_examples_for Tag do
|
|
570
573
|
tag_class.find_or_create_by_path(%w(a b2 c3))
|
571
574
|
a, b1, b2, c1, c2, c3 = %w(a b1 b2 c1 c2 c3).map { |ea| tag_class.where(:name => ea).first.id }
|
572
575
|
dot = tag_class.roots.first.to_dot_digraph
|
573
|
-
dot.
|
576
|
+
expect(dot).to eq <<-DOT
|
574
577
|
digraph G {
|
575
578
|
#{a} [label="a"]
|
576
579
|
#{a} -> #{b1}
|