closure_tree 4.0.1 → 4.1.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.
- data/README.md +30 -4
- data/Rakefile +5 -1
- data/lib/closure_tree/acts_as_tree.rb +1 -1
- data/lib/closure_tree/model.rb +63 -33
- data/lib/closure_tree/numeric_deterministic_ordering.rb +2 -2
- data/lib/closure_tree/support.rb +52 -16
- data/lib/closure_tree/version.rb +1 -1
- data/spec/db/schema.rb +4 -4
- data/spec/label_spec.rb +3 -3
- data/spec/parallel_spec.rb +12 -6
- data/spec/spec_helper.rb +27 -6
- data/spec/support/models.rb +27 -5
- data/spec/tag_examples.rb +421 -0
- data/spec/tag_fixture_examples.rb +136 -0
- data/spec/tag_spec.rb +4 -480
- data/spec/user_spec.rb +8 -8
- data/spec/uuid_tag_spec.rb +6 -0
- metadata +173 -172
- checksums.yaml +0 -15
- data/spec/hash_tree_spec.rb +0 -91
@@ -0,0 +1,136 @@
|
|
1
|
+
shared_examples_for 'Tag (with fixtures)' do
|
2
|
+
|
3
|
+
let (:tag_class) { described_class }
|
4
|
+
let (:tag_hierarchy_class) { described_class.hierarchy_class }
|
5
|
+
|
6
|
+
describe 'Tag (with fixtures)' do
|
7
|
+
fixtures :tags
|
8
|
+
before :each do
|
9
|
+
tag_class.rebuild!
|
10
|
+
DestroyedTag.delete_all
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'adding children' do
|
14
|
+
it 'should work explicitly' do
|
15
|
+
sb = Tag.create!(:name => 'Santa Barbara')
|
16
|
+
sb.leaf?.should_not be_nil
|
17
|
+
tags(:california).add_child sb
|
18
|
+
sb.leaf?.should_not be_nil
|
19
|
+
validate_city_tag sb
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should work implicitly through the collection' do
|
23
|
+
eg = Tag.create!(:name => 'El Granada')
|
24
|
+
eg.leaf?.should_not be_nil
|
25
|
+
tags(:california).children << eg
|
26
|
+
eg.leaf?.should_not be_nil
|
27
|
+
validate_city_tag eg
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should fail to create ancestor loops' do
|
31
|
+
child = tags(:child)
|
32
|
+
parent = child.parent
|
33
|
+
child.add_child(parent) # this should fail
|
34
|
+
parent.valid?.should be_false
|
35
|
+
child.reload.children.should be_empty
|
36
|
+
parent.reload.children.should == [child]
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should move non-leaves' do
|
40
|
+
# This is what the fixture should encode:
|
41
|
+
tags(:d2).ancestry_path.should == %w{a1 b2 c2 d2}
|
42
|
+
tags(:b1).add_child(tags(:c2))
|
43
|
+
tags(:b2).leaf?.should_not be_nil
|
44
|
+
tags(:b1).children.include?(tags(:c2)).should be_true
|
45
|
+
tags(:d2).reload.ancestry_path.should == %w{a1 b1 c2 d2}
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should move leaves' do
|
49
|
+
l = Tag.find_or_create_by_path(%w{leaftest branch1 leaf})
|
50
|
+
b2 = Tag.find_or_create_by_path(%w{leaftest branch2})
|
51
|
+
b2.children << l
|
52
|
+
l.ancestry_path.should == %w{leaftest branch2 leaf}
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should move roots' do
|
56
|
+
l1 = Tag.find_or_create_by_path(%w{roottest1 branch1 leaf1})
|
57
|
+
l2 = Tag.find_or_create_by_path(%w{roottest2 branch2 leaf2})
|
58
|
+
l1.children << l2.root
|
59
|
+
l1.reload.ancestry_path.should == %w{roottest1 branch1 leaf1}
|
60
|
+
l2.reload.ancestry_path.should == %w{roottest1 branch1 leaf1 roottest2 branch2 leaf2}
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should cascade delete all children' do
|
64
|
+
b2 = tags(:b2)
|
65
|
+
entities = b2.self_and_descendants.to_a
|
66
|
+
names = b2.self_and_descendants.collect { |t| t.name }
|
67
|
+
b2.destroy
|
68
|
+
entities.each { |e| Tag.find_by_id(e.id).should be_nil }
|
69
|
+
DestroyedTag.all.collect { |t| t.name }.should =~ names
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'injected attributes' do
|
74
|
+
it 'should compute level correctly' do
|
75
|
+
tags(:grandparent).level.should == 0
|
76
|
+
tags(:parent).level.should == 1
|
77
|
+
tags(:child).level.should == 2
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should determine parent correctly' do
|
81
|
+
tags(:grandparent).parent.should == nil
|
82
|
+
tags(:parent).parent.should == tags(:grandparent)
|
83
|
+
tags(:child).parent.should == tags(:parent)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should have a sane children collection' do
|
87
|
+
tags(:grandparent).children.include? tags(:parent).should be_true
|
88
|
+
tags(:parent).children.include? tags(:child).should be_true
|
89
|
+
tags(:child).children.should be_empty
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'assembles siblings correctly' do
|
93
|
+
tags(:b1).siblings.to_a.should =~ [tags(:b2)]
|
94
|
+
tags(:a1).siblings.to_a.should =~ (Tag.roots.to_a - [tags(:a1)])
|
95
|
+
tags(:a1).self_and_siblings.to_a.should =~ Tag.roots.to_a
|
96
|
+
|
97
|
+
# must be ordered
|
98
|
+
tags(:indoor).siblings.to_a.should == [tags(:home), tags(:museum), tags(:outdoor), tags(:united_states)]
|
99
|
+
tags(:indoor).self_and_siblings.to_a.should == [tags(:home), tags(:indoor), tags(:museum), tags(:outdoor), tags(:united_states)]
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'assembles siblings before correctly' do
|
103
|
+
tags(:home).siblings_before.to_a.should == []
|
104
|
+
tags(:indoor).siblings_before.to_a.should == [tags(:home)]
|
105
|
+
tags(:outdoor).siblings_before.to_a.should == [tags(:home), tags(:indoor), tags(:museum)]
|
106
|
+
tags(:united_states).siblings_before.to_a.should == [tags(:home), tags(:indoor), tags(:museum), tags(:outdoor)]
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'assembles siblings after correctly' do
|
110
|
+
tags(:indoor).siblings_after.to_a.should == [tags(:museum), tags(:outdoor), tags(:united_states)]
|
111
|
+
tags(:outdoor).siblings_after.to_a.should == [tags(:united_states)]
|
112
|
+
tags(:united_states).siblings_after.to_a.should == []
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'assembles ancestors' do
|
116
|
+
tags(:child).ancestors.should == [tags(:parent), tags(:grandparent)]
|
117
|
+
tags(:child).self_and_ancestors.should == [tags(:child), tags(:parent), tags(:grandparent)]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'assembles descendants' do
|
121
|
+
tags(:parent).descendants.should == [tags(:child)]
|
122
|
+
tags(:parent).self_and_descendants.should == [tags(:parent), tags(:child)]
|
123
|
+
tags(:grandparent).descendants.should == [tags(:parent), tags(:child)]
|
124
|
+
tags(:grandparent).self_and_descendants.should == [tags(:grandparent), tags(:parent), tags(:child)]
|
125
|
+
tags(:grandparent).self_and_descendants.collect { |t| t.name }.join(" > ").should == 'grandparent > parent > child'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def validate_city_tag city
|
130
|
+
tags(:california).children.include?(city).should_not be_nil
|
131
|
+
city.ancestors.should == [tags(:california), tags(:united_states), tags(:places)]
|
132
|
+
city.self_and_ancestors.should == [city, tags(:california), tags(:united_states), tags(:places)]
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
data/spec/tag_spec.rb
CHANGED
@@ -1,484 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
it "has correct accessible_attributes" do
|
6
|
-
Tag.accessible_attributes.to_a.should =~ %w(parent name)
|
7
|
-
end
|
8
|
-
|
9
|
-
describe "empty db" do
|
10
|
-
|
11
|
-
def nuke_db
|
12
|
-
TagHierarchy.delete_all
|
13
|
-
Tag.delete_all
|
14
|
-
end
|
15
|
-
|
16
|
-
before :each do
|
17
|
-
nuke_db
|
18
|
-
end
|
19
|
-
|
20
|
-
context "empty db" do
|
21
|
-
it "should return no entities" do
|
22
|
-
Tag.roots.should be_empty
|
23
|
-
Tag.leaves.should be_empty
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
context "1 tag db" do
|
28
|
-
it "should return the only entity as a root and leaf" do
|
29
|
-
a = Tag.create!(:name => "a")
|
30
|
-
Tag.roots.should == [a]
|
31
|
-
Tag.leaves.should == [a]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "2 tag db" do
|
36
|
-
before :each do
|
37
|
-
@root = Tag.create!(:name => "root")
|
38
|
-
@leaf = @root.add_child(Tag.create!(:name => "leaf"))
|
39
|
-
end
|
40
|
-
it "should return a simple root and leaf" do
|
41
|
-
Tag.roots.should == [@root]
|
42
|
-
Tag.leaves.should == [@leaf]
|
43
|
-
end
|
44
|
-
it "should return child_ids for root" do
|
45
|
-
@root.child_ids.should == [@leaf.id]
|
46
|
-
end
|
47
|
-
it "should return an empty array for leaves" do
|
48
|
-
@leaf.child_ids.should be_empty
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
context "3 tag collection.create db" do
|
53
|
-
before :each do
|
54
|
-
@root = Tag.create! :name => "root"
|
55
|
-
@mid = @root.children.create! :name => "mid"
|
56
|
-
@leaf = @mid.children.create! :name => "leaf"
|
57
|
-
DestroyedTag.delete_all
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should create all tags" do
|
61
|
-
Tag.all.should =~ [@root, @mid, @leaf]
|
62
|
-
end
|
63
|
-
|
64
|
-
it "should return a root and leaf without middle tag" do
|
65
|
-
Tag.roots.should == [@root]
|
66
|
-
Tag.leaves.should == [@leaf]
|
67
|
-
end
|
68
|
-
|
69
|
-
it "should delete leaves" do
|
70
|
-
Tag.leaves.destroy_all
|
71
|
-
Tag.roots.should == [@root] # untouched
|
72
|
-
Tag.leaves.should == [@mid]
|
73
|
-
end
|
74
|
-
|
75
|
-
it "should delete everything if you delete the roots" do
|
76
|
-
Tag.roots.destroy_all
|
77
|
-
Tag.all.should be_empty
|
78
|
-
Tag.roots.should be_empty
|
79
|
-
Tag.leaves.should be_empty
|
80
|
-
DestroyedTag.all.collect { |t| t.name }.should =~ %w{root mid leaf}
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
context "3 tag explicit_create db" do
|
85
|
-
before :each do
|
86
|
-
@root = Tag.create!(:name => "root")
|
87
|
-
@mid = @root.add_child(Tag.create!(:name => "mid"))
|
88
|
-
@leaf = @mid.add_child(Tag.create!(:name => "leaf"))
|
89
|
-
end
|
90
|
-
|
91
|
-
it "should create all tags" do
|
92
|
-
Tag.all.should =~ [@root, @mid, @leaf]
|
93
|
-
end
|
94
|
-
|
95
|
-
it "should return a root and leaf without middle tag" do
|
96
|
-
Tag.roots.should == [@root]
|
97
|
-
Tag.leaves.should == [@leaf]
|
98
|
-
end
|
99
|
-
|
100
|
-
it "should prevent parental loops from torso" do
|
101
|
-
@mid.children << @root
|
102
|
-
@root.valid?.should be_false
|
103
|
-
@mid.reload.children.should == [@leaf]
|
104
|
-
end
|
105
|
-
|
106
|
-
it "should prevent parental loops from toes" do
|
107
|
-
@leaf.children << @root
|
108
|
-
@root.valid?.should be_false
|
109
|
-
@leaf.reload.children.should be_empty
|
110
|
-
end
|
111
|
-
|
112
|
-
it "should support re-parenting" do
|
113
|
-
@root.children << @leaf
|
114
|
-
Tag.leaves.should == [@leaf, @mid]
|
115
|
-
end
|
116
|
-
|
117
|
-
it "cleans up hierarchy references for leaves" do
|
118
|
-
@leaf.destroy
|
119
|
-
TagHierarchy.find_all_by_ancestor_id(@leaf.id).should be_empty
|
120
|
-
TagHierarchy.find_all_by_descendant_id(@leaf.id).should be_empty
|
121
|
-
end
|
122
|
-
|
123
|
-
it "cleans up hierarchy references" do
|
124
|
-
@mid.destroy
|
125
|
-
TagHierarchy.find_all_by_ancestor_id(@mid.id).should be_empty
|
126
|
-
TagHierarchy.find_all_by_descendant_id(@mid.id).should be_empty
|
127
|
-
@root.reload.should be_root
|
128
|
-
root_hiers = @root.ancestor_hierarchies.to_a
|
129
|
-
root_hiers.size.should == 1
|
130
|
-
TagHierarchy.find_all_by_ancestor_id(@root.id).should == root_hiers
|
131
|
-
TagHierarchy.find_all_by_descendant_id(@root.id).should == root_hiers
|
132
|
-
end
|
133
|
-
|
134
|
-
it "should have different hash codes for each hierarchy model" do
|
135
|
-
hashes = TagHierarchy.all.map(&:hash)
|
136
|
-
hashes.should =~ hashes.uniq
|
137
|
-
end
|
138
|
-
|
139
|
-
it "should return the same hash code for equal hierarchy models" do
|
140
|
-
TagHierarchy.first.hash.should == TagHierarchy.first.hash
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
it "performs as the readme says it does" do
|
145
|
-
grandparent = Tag.create(:name => 'Grandparent')
|
146
|
-
parent = grandparent.children.create(:name => 'Parent')
|
147
|
-
child1 = Tag.create(:name => 'First Child', :parent => parent)
|
148
|
-
child2 = Tag.new(:name => 'Second Child')
|
149
|
-
parent.children << child2
|
150
|
-
child3 = Tag.new(:name => 'Third Child')
|
151
|
-
parent.add_child child3
|
152
|
-
grandparent.self_and_descendants.collect(&:name).should ==
|
153
|
-
["Grandparent", "Parent", "First Child", "Second Child", "Third Child"]
|
154
|
-
child1.ancestry_path.should ==
|
155
|
-
["Grandparent", "Parent", "First Child"]
|
156
|
-
child3.ancestry_path.should ==
|
157
|
-
["Grandparent", "Parent", "Third Child"]
|
158
|
-
d = Tag.find_or_create_by_path %w(a b c d)
|
159
|
-
h = Tag.find_or_create_by_path %w(e f g h)
|
160
|
-
e = h.root
|
161
|
-
d.add_child(e) # "d.children << e" would work too, of course
|
162
|
-
h.ancestry_path.should == %w(a b c d e f g h)
|
163
|
-
end
|
164
|
-
|
165
|
-
context "roots" do
|
166
|
-
it "sorts alphabetically" do
|
167
|
-
expected = ("a".."z").to_a
|
168
|
-
expected.shuffle.each { |ea| Tag.create!(:name => ea) }
|
169
|
-
Tag.roots.collect { |ea| ea.name }.should == expected
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
context "paths" do
|
174
|
-
before :each do
|
175
|
-
@child = Tag.find_or_create_by_path(%w(grandparent parent child))
|
176
|
-
@child.title = "Kid"
|
177
|
-
@parent = @child.parent
|
178
|
-
@parent.title = "Mom"
|
179
|
-
@grandparent = @parent.parent
|
180
|
-
@grandparent.title = "Nonnie"
|
181
|
-
[@child, @parent, @grandparent].each { |ea| ea.save! }
|
182
|
-
end
|
183
|
-
|
184
|
-
it "should build ancestry path" do
|
185
|
-
@child.ancestry_path.should == %w{grandparent parent child}
|
186
|
-
@child.ancestry_path(:name).should == %w{grandparent parent child}
|
187
|
-
@child.ancestry_path(:title).should == %w{Nonnie Mom Kid}
|
188
|
-
end
|
189
|
-
|
190
|
-
it "should find by path" do
|
191
|
-
# class method:
|
192
|
-
Tag.find_by_path(%w{grandparent parent child}).should == @child
|
193
|
-
# instance method:
|
194
|
-
@parent.find_by_path(%w{child}).should == @child
|
195
|
-
@grandparent.find_by_path(%w{parent child}).should == @child
|
196
|
-
@parent.find_by_path(%w{child larvae}).should be_nil
|
197
|
-
end
|
198
|
-
|
199
|
-
it "finds correctly rooted paths" do
|
200
|
-
decoy = Tag.find_or_create_by_path %w(a b c d)
|
201
|
-
b_d = Tag.find_or_create_by_path %w(b c d)
|
202
|
-
Tag.find_by_path(%w(b c d)).should == b_d
|
203
|
-
Tag.find_by_path(%w(c d)).should be_nil
|
204
|
-
end
|
205
|
-
|
206
|
-
it "find_by_path for 1 node" do
|
207
|
-
b = Tag.find_or_create_by_path %w(a b)
|
208
|
-
b2 = b.root.find_by_path(%w(b))
|
209
|
-
b2.should == b
|
210
|
-
end
|
211
|
-
|
212
|
-
it "find_by_path for 2 nodes" do
|
213
|
-
c = Tag.find_or_create_by_path %w(a b c)
|
214
|
-
c.root.find_by_path(%w(b c)).should == c
|
215
|
-
c.root.find_by_path(%w(a c)).should be_nil
|
216
|
-
c.root.find_by_path(%w(c)).should be_nil
|
217
|
-
end
|
218
|
-
|
219
|
-
it "find_by_path for 3 nodes" do
|
220
|
-
d = Tag.find_or_create_by_path %w(a b c d)
|
221
|
-
d.root.find_by_path(%w(b c d)).should == d
|
222
|
-
Tag.find_by_path(%w(a b c d)).should == d
|
223
|
-
Tag.find_by_path(%w(d)).should be_nil
|
224
|
-
end
|
225
|
-
|
226
|
-
it "should return nil for missing nodes" do
|
227
|
-
Tag.find_by_path(%w{missing}).should be_nil
|
228
|
-
Tag.find_by_path(%w{grandparent missing}).should be_nil
|
229
|
-
Tag.find_by_path(%w{grandparent parent missing}).should be_nil
|
230
|
-
Tag.find_by_path(%w{grandparent parent missing child}).should be_nil
|
231
|
-
end
|
232
|
-
|
233
|
-
it "should find or create by path" do
|
234
|
-
# class method:
|
235
|
-
grandparent = Tag.find_or_create_by_path(%w{grandparent})
|
236
|
-
grandparent.should == @grandparent
|
237
|
-
child = Tag.find_or_create_by_path(%w{grandparent parent child})
|
238
|
-
child.should == @child
|
239
|
-
Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
|
240
|
-
a = Tag.find_or_create_by_path(%w{a})
|
241
|
-
a.ancestry_path.should == %w{a}
|
242
|
-
# instance method:
|
243
|
-
a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
shared_examples_for "Tag (2)" do
|
250
|
-
describe "Tag (2)" do
|
251
|
-
|
252
|
-
fixtures :tags
|
253
|
-
|
254
|
-
before :each do
|
255
|
-
Tag.rebuild!
|
256
|
-
DestroyedTag.delete_all
|
257
|
-
end
|
258
|
-
|
259
|
-
context "class injection" do
|
260
|
-
it "should build hierarchy classname correctly" do
|
261
|
-
Tag.hierarchy_class.to_s.should == "TagHierarchy"
|
262
|
-
Tag._ct.hierarchy_class_name.should == "TagHierarchy"
|
263
|
-
Tag._ct.short_hierarchy_class_name.should == "TagHierarchy"
|
264
|
-
end
|
265
|
-
|
266
|
-
it "should have a correct parent column name" do
|
267
|
-
Tag._ct.parent_column_name.should == "parent_id"
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
context "roots" do
|
272
|
-
it "should find global roots" do
|
273
|
-
roots = Tag.roots.to_a
|
274
|
-
roots.should be_member(tags(:people))
|
275
|
-
roots.should be_member(tags(:events))
|
276
|
-
roots.should_not be_member(tags(:child))
|
277
|
-
tags(:people).root?.should be_true
|
278
|
-
tags(:child).root?.should be_false
|
279
|
-
end
|
280
|
-
|
281
|
-
it "should find an instance root" do
|
282
|
-
tags(:grandparent).root.should == tags(:grandparent)
|
283
|
-
tags(:parent).root.should == tags(:grandparent)
|
284
|
-
tags(:child).root.should == tags(:grandparent)
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
context "leaves" do
|
289
|
-
it "should assemble global leaves" do
|
290
|
-
Tag.leaves.size.should > 0
|
291
|
-
Tag.leaves.each { |t| t.children.should be_empty, "#{t.name} was returned by leaves but has children: #{t.children}" }
|
292
|
-
Tag.leaves.each { |t| t.should be_leaf, "{t.name} was returned by leaves but was not a leaf" }
|
293
|
-
end
|
294
|
-
|
295
|
-
it "should assemble instance leaves" do
|
296
|
-
tags(:grandparent).leaves.should == [tags(:child)]
|
297
|
-
tags(:parent).leaves.should == [tags(:child)]
|
298
|
-
tags(:child).leaves.should == [tags(:child)]
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
context "adding children" do
|
303
|
-
it "should work explicitly" do
|
304
|
-
sb = Tag.create!(:name => "Santa Barbara")
|
305
|
-
sb.leaf?.should_not be_nil
|
306
|
-
tags(:california).add_child sb
|
307
|
-
sb.leaf?.should_not be_nil
|
308
|
-
validate_city_tag sb
|
309
|
-
end
|
310
|
-
|
311
|
-
it "should work implicitly through the collection" do
|
312
|
-
eg = Tag.create!(:name => "El Granada")
|
313
|
-
eg.leaf?.should_not be_nil
|
314
|
-
tags(:california).children << eg
|
315
|
-
eg.leaf?.should_not be_nil
|
316
|
-
validate_city_tag eg
|
317
|
-
end
|
318
|
-
|
319
|
-
it "should fail to create ancestor loops" do
|
320
|
-
child = tags(:child)
|
321
|
-
parent = child.parent
|
322
|
-
child.add_child(parent) # this should fail
|
323
|
-
parent.valid?.should be_false
|
324
|
-
child.reload.children.should be_empty
|
325
|
-
parent.reload.children.should == [child]
|
326
|
-
end
|
327
|
-
|
328
|
-
it "should move non-leaves" do
|
329
|
-
# This is what the fixture should encode:
|
330
|
-
tags(:d2).ancestry_path.should == %w{a1 b2 c2 d2}
|
331
|
-
tags(:b1).add_child(tags(:c2))
|
332
|
-
tags(:b2).leaf?.should_not be_nil
|
333
|
-
tags(:b1).children.include?(tags(:c2)).should be_true
|
334
|
-
tags(:d2).reload.ancestry_path.should == %w{a1 b1 c2 d2}
|
335
|
-
end
|
336
|
-
|
337
|
-
it "should move leaves" do
|
338
|
-
l = Tag.find_or_create_by_path(%w{leaftest branch1 leaf})
|
339
|
-
b2 = Tag.find_or_create_by_path(%w{leaftest branch2})
|
340
|
-
b2.children << l
|
341
|
-
l.ancestry_path.should == %w{leaftest branch2 leaf}
|
342
|
-
end
|
343
|
-
|
344
|
-
it "should move roots" do
|
345
|
-
l1 = Tag.find_or_create_by_path(%w{roottest1 branch1 leaf1})
|
346
|
-
l2 = Tag.find_or_create_by_path(%w{roottest2 branch2 leaf2})
|
347
|
-
l1.children << l2.root
|
348
|
-
l1.reload.ancestry_path.should == %w{roottest1 branch1 leaf1}
|
349
|
-
l2.reload.ancestry_path.should == %w{roottest1 branch1 leaf1 roottest2 branch2 leaf2}
|
350
|
-
end
|
351
|
-
|
352
|
-
it "should cascade delete all children" do
|
353
|
-
b2 = tags(:b2)
|
354
|
-
entities = b2.self_and_descendants.to_a
|
355
|
-
names = b2.self_and_descendants.collect { |t| t.name }
|
356
|
-
b2.destroy
|
357
|
-
entities.each { |e| Tag.find_by_id(e.id).should be_nil }
|
358
|
-
DestroyedTag.all.collect { |t| t.name }.should =~ names
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
context "injected attributes" do
|
363
|
-
it "should compute level correctly" do
|
364
|
-
tags(:grandparent).level.should == 0
|
365
|
-
tags(:parent).level.should == 1
|
366
|
-
tags(:child).level.should == 2
|
367
|
-
end
|
368
|
-
|
369
|
-
it "should determine parent correctly" do
|
370
|
-
tags(:grandparent).parent.should == nil
|
371
|
-
tags(:parent).parent.should == tags(:grandparent)
|
372
|
-
tags(:child).parent.should == tags(:parent)
|
373
|
-
end
|
374
|
-
|
375
|
-
it "should have a sane children collection" do
|
376
|
-
tags(:grandparent).children.include? tags(:parent).should be_true
|
377
|
-
tags(:parent).children.include? tags(:child).should be_true
|
378
|
-
tags(:child).children.should be_empty
|
379
|
-
end
|
380
|
-
|
381
|
-
it "assembles siblings correctly" do
|
382
|
-
tags(:b1).siblings.to_a.should =~ [tags(:b2)]
|
383
|
-
tags(:a1).siblings.to_a.should =~ (Tag.roots.to_a - [tags(:a1)])
|
384
|
-
tags(:a1).self_and_siblings.to_a.should =~ Tag.roots.to_a
|
385
|
-
|
386
|
-
# must be ordered
|
387
|
-
tags(:indoor).siblings.to_a.should == [tags(:home), tags(:museum), tags(:outdoor), tags(:united_states)]
|
388
|
-
tags(:indoor).self_and_siblings.to_a.should == [tags(:home), tags(:indoor), tags(:museum), tags(:outdoor), tags(:united_states)]
|
389
|
-
end
|
390
|
-
|
391
|
-
it "assembles siblings before correctly" do
|
392
|
-
tags(:home).siblings_before.to_a.should == []
|
393
|
-
tags(:indoor).siblings_before.to_a.should == [tags(:home)]
|
394
|
-
tags(:outdoor).siblings_before.to_a.should == [tags(:home), tags(:indoor), tags(:museum)]
|
395
|
-
tags(:united_states).siblings_before.to_a.should == [tags(:home), tags(:indoor), tags(:museum), tags(:outdoor)]
|
396
|
-
end
|
397
|
-
|
398
|
-
it "assembles siblings after correctly" do
|
399
|
-
tags(:indoor).siblings_after.to_a.should == [tags(:museum), tags(:outdoor), tags(:united_states)]
|
400
|
-
tags(:outdoor).siblings_after.to_a.should == [tags(:united_states)]
|
401
|
-
tags(:united_states).siblings_after.to_a.should == []
|
402
|
-
end
|
403
|
-
|
404
|
-
it "assembles ancestors" do
|
405
|
-
tags(:child).ancestors.should == [tags(:parent), tags(:grandparent)]
|
406
|
-
tags(:child).self_and_ancestors.should == [tags(:child), tags(:parent), tags(:grandparent)]
|
407
|
-
end
|
408
|
-
|
409
|
-
it "assembles descendants" do
|
410
|
-
tags(:parent).descendants.should == [tags(:child)]
|
411
|
-
tags(:parent).self_and_descendants.should == [tags(:parent), tags(:child)]
|
412
|
-
tags(:grandparent).descendants.should == [tags(:parent), tags(:child)]
|
413
|
-
tags(:grandparent).self_and_descendants.should == [tags(:grandparent), tags(:parent), tags(:child)]
|
414
|
-
tags(:grandparent).self_and_descendants.collect { |t| t.name }.join(" > ").should == "grandparent > parent > child"
|
415
|
-
end
|
416
|
-
end
|
417
|
-
|
418
|
-
def validate_city_tag city
|
419
|
-
tags(:california).children.include?(city).should_not be_nil
|
420
|
-
city.ancestors.should == [tags(:california), tags(:united_states), tags(:places)]
|
421
|
-
city.self_and_ancestors.should == [city, tags(:california), tags(:united_states), tags(:places)]
|
422
|
-
end
|
423
|
-
|
424
|
-
end
|
425
|
-
end
|
2
|
+
require 'tag_examples'
|
3
|
+
require 'tag_fixture_examples'
|
426
4
|
|
427
5
|
describe Tag do
|
428
|
-
|
429
|
-
|
430
|
-
Tag.ancestors.should_not include(ActiveModel::ForbiddenAttributesProtection)
|
431
|
-
end
|
432
|
-
end
|
433
|
-
it_behaves_like "Tag (1)"
|
434
|
-
it_behaves_like "Tag (2)"
|
435
|
-
end
|
436
|
-
|
437
|
-
describe "Tag with AR whitelisted attributes enabled" do
|
438
|
-
before(:all) do
|
439
|
-
ActiveRecord::Base.attr_accessible(nil) # turn on whitelisted attributes
|
440
|
-
ActiveRecord::Base.descendants.each { |ea| ea.reset_column_information }
|
441
|
-
end
|
442
|
-
it "should not include ActiveModel::ForbiddenAttributesProtection" do
|
443
|
-
if defined?(ActiveModel::ForbiddenAttributesProtection)
|
444
|
-
Tag.ancestors.should_not include(ActiveModel::ForbiddenAttributesProtection)
|
445
|
-
end
|
446
|
-
end
|
447
|
-
it_behaves_like "Tag (1)"
|
448
|
-
it_behaves_like "Tag (2)"
|
449
|
-
end
|
450
|
-
|
451
|
-
# This has to be the last one, because we include strong parameters into Tag
|
452
|
-
describe "Tag with strong parameters" do
|
453
|
-
before(:all) do
|
454
|
-
require 'strong_parameters'
|
455
|
-
class Tag
|
456
|
-
include ActiveModel::ForbiddenAttributesProtection
|
457
|
-
end
|
458
|
-
end
|
459
|
-
it_behaves_like "Tag (1)"
|
460
|
-
it_behaves_like "Tag (2)"
|
461
|
-
end
|
462
|
-
|
463
|
-
describe "Tag with UUID" do
|
464
|
-
before(:all) do
|
465
|
-
# Change tables
|
466
|
-
Tag.table_name = Tag.table_name.gsub('tags', 'tags_uuid')
|
467
|
-
Tag.reset_column_information
|
468
|
-
TagHierarchy.table_name = TagHierarchy.table_name.gsub('tag_hierarchies', 'tag_hierarchies_uuid')
|
469
|
-
TagHierarchy.reset_column_information
|
470
|
-
|
471
|
-
# We have to reset a few other caches
|
472
|
-
Tag._ct.options[:hierarchy_table_name] = 'tag_hierarchies_uuid'
|
473
|
-
Tag.reflections.each do |key, ref|
|
474
|
-
ref.instance_variable_set('@table_name', nil)
|
475
|
-
ref.instance_variable_set('@quoted_table_name', nil)
|
476
|
-
ref.options[:order].sub! 'tag_hierarchies', 'tag_hierarchies_uuid' if ref.options[:order]
|
477
|
-
end
|
478
|
-
|
479
|
-
# Add ID
|
480
|
-
Tag.before_create { self.id = UUIDTools::UUID.random_create.to_s }
|
481
|
-
end
|
482
|
-
|
483
|
-
it_behaves_like "Tag (1)"
|
6
|
+
it_behaves_like 'Tag (without fixtures)'
|
7
|
+
it_behaves_like 'Tag (with fixtures)'
|
484
8
|
end
|