closure_tree 4.6.2 → 4.6.3
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 +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}
|