chrislloyd-eb_nested_set 0.3.2
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/LICENSE +20 -0
- data/README.md +65 -0
- data/Rakefile +87 -0
- data/init.rb +1 -0
- data/lib/eb_nested_set.rb +349 -0
- data/spec/directory_spec.rb +54 -0
- data/spec/employee_spec.rb +74 -0
- data/spec/nested_set_behavior.rb +586 -0
- data/spec/spec_helper.rb +83 -0
- metadata +64 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/nested_set_behavior'
|
3
|
+
|
4
|
+
class Directory < ActiveRecord::Base
|
5
|
+
acts_as_nested_set :left => :lft, :right => :rgt
|
6
|
+
|
7
|
+
validates_presence_of :name
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Directory do
|
11
|
+
|
12
|
+
def invalid_attributes(options = {})
|
13
|
+
return { }.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_attributes(options = {})
|
17
|
+
$directory_no = $directory_no ? $directory_no + 1 : 0
|
18
|
+
return { :name => "directory#{$directory_no}" }.merge(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
@model = Directory
|
23
|
+
@instance = @model.new
|
24
|
+
end
|
25
|
+
|
26
|
+
it_should_behave_like "all nested set models"
|
27
|
+
|
28
|
+
it "should throw an error when attempting to assign lft directly" do
|
29
|
+
lambda {
|
30
|
+
@instance.lft = 42
|
31
|
+
}.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
|
32
|
+
@instance.lft.should_not == 42
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should throw an error when attempting to assign rgt directly" do
|
36
|
+
lambda {
|
37
|
+
@instance.rgt = 42
|
38
|
+
}.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
|
39
|
+
@instance.rgt.should_not == 42
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should throw an error when mass assigning to lft" do
|
43
|
+
lambda {
|
44
|
+
@model.new(valid_attributes(:lft => 1))
|
45
|
+
}.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should throw an error when mass assigning to rgt" do
|
49
|
+
lambda {
|
50
|
+
@model.new(valid_attributes(:rgt => 1))
|
51
|
+
}.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/nested_set_behavior'
|
3
|
+
|
4
|
+
class Employee < ActiveRecord::Base
|
5
|
+
acts_as_nested_set :scope => :company
|
6
|
+
|
7
|
+
validates_presence_of :name
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Employee, "with nested sets for two different companies" do
|
11
|
+
before do
|
12
|
+
# Company 1...
|
13
|
+
Employee.with_options :company_id => 1 do |c1|
|
14
|
+
@c1_1 = c1.create!(:name => "Company 1 - 1")
|
15
|
+
@c1_2 = c1.create!(:name => "Company 1 - 2")
|
16
|
+
|
17
|
+
@c1_11 = c1.create!(:name => "Company 1 - 11", :parent => @c1_1)
|
18
|
+
@c1_12 = c1.create!(:name => "Company 1 - 12", :parent => @c1_1)
|
19
|
+
|
20
|
+
@c1_111 = c1.create!(:name => "Company 1 - 111", :parent => @c1_11)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Company 2...
|
24
|
+
Employee.with_options :company_id => 2 do |c2|
|
25
|
+
@c2_1 = c2.create!(:name => "Company 2 - 1")
|
26
|
+
@c2_11 = c2.create!(:name => "Company 2 - 11", :parent => @c2_1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
Employee.delete_all
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should not allow a new employee in one company to be a child of an employee in the other company, when parent is assigned to" do
|
35
|
+
@employee = Employee.create(:company_id => 1, :parent => @c2_11)
|
36
|
+
@employee.errors[:parent_id].should_not be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not allow a new employee in one company to be a child of an employee in the other company, when parent_id is assigned to" do
|
40
|
+
@employee = Employee.create(:company_id => 1, :parent_id => @c2_11.id)
|
41
|
+
@employee.errors[:parent_id].should_not be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not allow an existing employee in one company to become a child of an employee in the other company, when parent is assigned to" do
|
45
|
+
@c1_11.parent = @c2_11
|
46
|
+
@c1_11.save
|
47
|
+
@c1_11.errors[:parent_id].should_not be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not allow an existing employee in one company to become a child of an employee in the other company, when parent_id is assigned to" do
|
51
|
+
@c1_11.parent_id = @c2_11.id
|
52
|
+
@c1_11.save
|
53
|
+
@c1_11.errors[:parent_id].should_not be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should keep the tree for company 1 and for company 2 entirely disjoint" do
|
57
|
+
c1_tree = (@c1_1.family + @c1_2.family).flatten
|
58
|
+
c2_tree = @c2_1.family
|
59
|
+
|
60
|
+
(c1_tree & c2_tree).should be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return the correct descendants when retrieving via a database query" do
|
64
|
+
@c1_1.descendants.should == [@c1_11, @c1_111, @c1_12]
|
65
|
+
@c1_2.descendants.should == []
|
66
|
+
@c2_1.descendants.should == [@c2_11]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return the correct levels when retrieving via a database query" do
|
70
|
+
@c1_1.family.map { |d| d.level }.should == [0, 1, 2, 1]
|
71
|
+
@c1_2.family.map { |d| d.level }.should == [0]
|
72
|
+
@c2_1.family.map { |d| d.level }.should == [0, 1]
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,586 @@
|
|
1
|
+
describe "all nested set models", :shared => true do
|
2
|
+
|
3
|
+
describe @model, 'model with acts_as_nested_set' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@instance = @model.new(valid_attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should change the parent_id in the database when a parent is assigned" do
|
10
|
+
without_changing_the_database do
|
11
|
+
@parent = @model.create!(valid_attributes)
|
12
|
+
|
13
|
+
@instance.parent = @parent
|
14
|
+
@instance.save!
|
15
|
+
@instance = @model.find(@instance.id)
|
16
|
+
|
17
|
+
@instance.parent_id.should == @parent.id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should change the parent_id in the database when a parent_id is assigned" do
|
22
|
+
without_changing_the_database do
|
23
|
+
@parent = @model.create!(valid_attributes)
|
24
|
+
|
25
|
+
@instance.parent_id = @parent.id
|
26
|
+
@instance.save!
|
27
|
+
@instance = @model.find(@instance.id)
|
28
|
+
|
29
|
+
@instance.parent_id.should == @parent.id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#bounds' do
|
34
|
+
|
35
|
+
it "should return a range, from left to right" do
|
36
|
+
without_changing_the_database do
|
37
|
+
@instance.save!
|
38
|
+
@instance.left.should == 1
|
39
|
+
@instance.right.should == 2
|
40
|
+
@instance.bounds.should == (1..2)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe @model, "with many descendants" do
|
49
|
+
before do
|
50
|
+
@r1 = @model.create!(valid_attributes)
|
51
|
+
@r2 = @model.create!(valid_attributes)
|
52
|
+
@r3 = @model.create!(valid_attributes)
|
53
|
+
|
54
|
+
@r1c1 = @model.create!(valid_attributes(:parent => @r1))
|
55
|
+
@r1c2 = @model.create!(valid_attributes(:parent => @r1))
|
56
|
+
@r1c3 = @model.create!(valid_attributes(:parent => @r1))
|
57
|
+
@r2c1 = @model.create!(valid_attributes(:parent => @r2))
|
58
|
+
|
59
|
+
@r1c1s1 = @model.create!(valid_attributes(:parent => @r1c1))
|
60
|
+
@r1c2s1 = @model.create!(valid_attributes(:parent => @r1c2))
|
61
|
+
@r1c2s2 = @model.create!(valid_attributes(:parent => @r1c2))
|
62
|
+
@r1c2s3 = @model.create!(valid_attributes(:parent => @r1c2))
|
63
|
+
|
64
|
+
@r1c2s2m1 = @model.create!(valid_attributes(:parent => @r1c2s2))
|
65
|
+
end
|
66
|
+
|
67
|
+
after do
|
68
|
+
@model.delete_all
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should find all root nodes" do
|
72
|
+
@model.roots.all.should == [@r1, @r2, @r3]
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should find a root nodes" do
|
76
|
+
@model.roots.first.should == @r1
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should maintain the integrity of the tree if a node is deleted" do
|
80
|
+
@r1c2.destroy
|
81
|
+
|
82
|
+
@r1.reload
|
83
|
+
@r1c3.reload
|
84
|
+
|
85
|
+
@r1.bounds.should == (1..8)
|
86
|
+
@r1c3.bounds.should == (6..7)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should maintain the integrity of the tree if a node is moved" do
|
90
|
+
@r1c2.parent_id = @r2.id
|
91
|
+
@r1c2.save!
|
92
|
+
|
93
|
+
reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
|
94
|
+
|
95
|
+
@r1.bounds.should == (1..8)
|
96
|
+
@r1c3.bounds.should == (6..7)
|
97
|
+
@r2.bounds.should == (9..22)
|
98
|
+
@r1c2.bounds.should == (12..21)
|
99
|
+
@r1c2s1.bounds.should == (13..14)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should change the parent, left and right in the database when a node is moved" do
|
103
|
+
|
104
|
+
@r1c2.parent_id = @r2.id
|
105
|
+
|
106
|
+
@r1c2.save!
|
107
|
+
@r1c2 = @model.find(@r1c2.id)
|
108
|
+
|
109
|
+
@r1c2.bounds.should == (12..21)
|
110
|
+
@r1c2.parent_id.should == @r2.id
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should maintain the integrity of the tree if a node is moved to a root position" do
|
114
|
+
@r1c2.parent_id = nil
|
115
|
+
@r1c2.save!
|
116
|
+
|
117
|
+
reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
|
118
|
+
|
119
|
+
@r1.bounds.should == (1..8)
|
120
|
+
@r1c3.bounds.should == (6..7)
|
121
|
+
@r1c2.bounds.should == (15..24)
|
122
|
+
@r1c2s1.bounds.should == (16..17)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should maintain the integrity of the tree if a node is moved to a root position by assigning a blank string (mass assignment)" do
|
126
|
+
@r1c2.parent_id = ""
|
127
|
+
@r1c2.save!
|
128
|
+
|
129
|
+
reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
|
130
|
+
|
131
|
+
@r1.bounds.should == (1..8)
|
132
|
+
@r1c3.bounds.should == (6..7)
|
133
|
+
@r1c2.bounds.should == (15..24)
|
134
|
+
@r1c2s1.bounds.should == (16..17)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should maintain the integrity of the tree if a root is to a non-root position" do
|
138
|
+
@r1c2.reload
|
139
|
+
@r2.parent_id = @r1c2.id
|
140
|
+
@r2.save!
|
141
|
+
|
142
|
+
reload_models(@r1, @r2, @r2c1, @r1c3, @r3, @r1c2)
|
143
|
+
|
144
|
+
@r1.bounds.should == (1..22)
|
145
|
+
@r1c2.bounds.should == (6..19)
|
146
|
+
@r1c3.bounds.should == (20..21)
|
147
|
+
@r3.bounds.should == (23..24)
|
148
|
+
@r2.bounds.should == (15..18)
|
149
|
+
@r2c1.bounds.should == (16..17)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should maintain the integrity of the tree if a node is moved through the parent association" do
|
153
|
+
@r1c2.parent = @r2
|
154
|
+
@r1c2.save!
|
155
|
+
|
156
|
+
reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
|
157
|
+
|
158
|
+
@r1.bounds.should == (1..8)
|
159
|
+
@r1c3.bounds.should == (6..7)
|
160
|
+
@r2.bounds.should == (9..22)
|
161
|
+
@r1c2.bounds.should == (12..21)
|
162
|
+
@r1c2s1.bounds.should == (13..14)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should maintain the integrity of the tree if a node is moved to a root position through the parent association" do
|
166
|
+
@r1c2.parent = nil
|
167
|
+
@r1c2.save!
|
168
|
+
|
169
|
+
reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
|
170
|
+
|
171
|
+
@r1.bounds.should == (1..8)
|
172
|
+
@r1c3.bounds.should == (6..7)
|
173
|
+
@r1c2.bounds.should == (15..24)
|
174
|
+
@r1c2s1.bounds.should == (16..17)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should maintain the integrity of the tree if a root is to a non-root position through the parent association" do
|
178
|
+
@r1c2.reload
|
179
|
+
@r2.parent = @r1c2
|
180
|
+
@r2.save!
|
181
|
+
|
182
|
+
reload_models(@r1, @r2, @r2c1, @r1c3, @r3, @r1c2)
|
183
|
+
|
184
|
+
@r1.bounds.should == (1..22)
|
185
|
+
@r1c2.bounds.should == (6..19)
|
186
|
+
@r1c3.bounds.should == (20..21)
|
187
|
+
@r3.bounds.should == (23..24)
|
188
|
+
@r2.bounds.should == (15..18)
|
189
|
+
@r2c1.bounds.should == (16..17)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should be invalid if parent is a descendant" do
|
193
|
+
@r2.parent = @r2c1
|
194
|
+
@r2.should_not be_valid
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should be invalid if parent is self" do
|
198
|
+
@r2.parent = @r2
|
199
|
+
@r2.should_not be_valid
|
200
|
+
end
|
201
|
+
|
202
|
+
describe ".nested_set" do
|
203
|
+
it "should find all nodes as a nested set and cache that data" do
|
204
|
+
roots = @model.nested_set
|
205
|
+
|
206
|
+
@model.delete_all
|
207
|
+
|
208
|
+
roots[0].should == @r1
|
209
|
+
roots[0].children[0].should == @r1c1
|
210
|
+
roots[0].children[0].children[0].should == @r1c1s1
|
211
|
+
roots[0].children[1].should == @r1c2
|
212
|
+
roots[0].children[1].children[0].should == @r1c2s1
|
213
|
+
roots[0].children[1].children[1].should == @r1c2s2
|
214
|
+
roots[0].children[1].children[1].children[0].should == @r1c2s2m1
|
215
|
+
roots[0].children[1].children[2].should == @r1c2s3
|
216
|
+
roots[0].children[2].should == @r1c3
|
217
|
+
roots[1].should == @r2
|
218
|
+
roots[1].children[0].should == @r2c1
|
219
|
+
roots[2].should == @r3
|
220
|
+
|
221
|
+
roots[1].children[0].parent.should == @r2
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe ".find_with_nested_set" do
|
226
|
+
it "should find a single node and cache it's descendants" do
|
227
|
+
node = @model.find_with_nested_set(@r1c2.id)
|
228
|
+
|
229
|
+
@model.delete_all
|
230
|
+
|
231
|
+
node.should == @r1c2
|
232
|
+
node.children[0].should == @r1c2s1
|
233
|
+
node.children[1].should == @r1c2s2
|
234
|
+
node.children[1].children[0].should == @r1c2s2m1
|
235
|
+
node.children[2].should == @r1c2s3
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should allow find with conditions" do
|
239
|
+
node = @model.find_with_nested_set(:first, :conditions => { :id => @r1c2.id })
|
240
|
+
|
241
|
+
@model.delete_all
|
242
|
+
|
243
|
+
node.should == @r1c2
|
244
|
+
node.children[0].should == @r1c2s1
|
245
|
+
node.children[1].should == @r1c2s2
|
246
|
+
node.children[1].children[0].should == @r1c2s2m1
|
247
|
+
node.children[2].should == @r1c2s3
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should allow find all with conditions" do
|
251
|
+
nodes = @model.find_with_nested_set(:all, :conditions => { :parent_id => @r1.id })
|
252
|
+
|
253
|
+
@model.delete_all
|
254
|
+
|
255
|
+
nodes[0].should == @r1c1
|
256
|
+
nodes[0].children[0].should == @r1c1s1
|
257
|
+
nodes[1].should == @r1c2
|
258
|
+
nodes[1].children[0].should == @r1c2s1
|
259
|
+
nodes[1].children[1].should == @r1c2s2
|
260
|
+
nodes[1].children[1].children[0].should == @r1c2s2m1
|
261
|
+
nodes[1].children[2].should == @r1c2s3
|
262
|
+
nodes[2].should == @r1c3
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe ".sort_nodes_to_nested_set" do
|
267
|
+
it "should accept a list of nodes and sort them to a nested set" do
|
268
|
+
roots = @model.sort_nodes_to_nested_set(@model.find(:all))
|
269
|
+
roots[0].should == @r1
|
270
|
+
roots[0].children[0].should == @r1c1
|
271
|
+
roots[0].children[0].children[0].should == @r1c1s1
|
272
|
+
roots[0].children[1].should == @r1c2
|
273
|
+
roots[0].children[1].children[0].should == @r1c2s1
|
274
|
+
roots[0].children[1].children[1].should == @r1c2s2
|
275
|
+
roots[0].children[1].children[1].children[0].should == @r1c2s2m1
|
276
|
+
roots[0].children[1].children[2].should == @r1c2s3
|
277
|
+
roots[0].children[2].should == @r1c3
|
278
|
+
roots[1].should == @r2
|
279
|
+
roots[1].children[0].should == @r2c1
|
280
|
+
roots[2].should == @r3
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
describe ".recalculate_nested_set" do
|
285
|
+
def values
|
286
|
+
@model.find(:all, :order => :id).map { |node| [node.left, node.right] }
|
287
|
+
end
|
288
|
+
|
289
|
+
before do
|
290
|
+
@model.find(:all, :order => :id).each do |i|
|
291
|
+
i.send(:set_boundaries, rand(1000), rand(1000))
|
292
|
+
i.save_without_validation!
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should correctly restore the left and right values for a messed up nested set" do
|
297
|
+
@model.recalculate_nested_set
|
298
|
+
[@r1, @r2, @r3].each(&:reload)
|
299
|
+
|
300
|
+
expected = [
|
301
|
+
[@r1c1, @r1c2, @r1c3, @r1c1s1, @r1c2s1, @r1c2s2, @r1c2s3, @r1c2s2m1],
|
302
|
+
[@r2c1],
|
303
|
+
[],
|
304
|
+
[@r1c1s1],
|
305
|
+
[@r1c2s1, @r1c2s2, @r1c2s3, @r1c2s2m1],
|
306
|
+
[],
|
307
|
+
[],
|
308
|
+
[],
|
309
|
+
[],
|
310
|
+
[@r1c2s2m1],
|
311
|
+
[],
|
312
|
+
[]
|
313
|
+
]
|
314
|
+
|
315
|
+
[@r1, @r2, @r3, @r1c1, @r1c2, @r1c3, @r2c1, @r1c1s1, @r1c2s1, @r1c2s2, @r1c2s3, @r1c2s2m1].each_with_index do |node, i|
|
316
|
+
node.descendants.find(:all, :order => :id).should == expected[i]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
it "should leave all records valid after running" do
|
321
|
+
@model.recalculate_nested_set
|
322
|
+
@model.find(:all).each do |node|
|
323
|
+
node.should be_valid
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
describe "#cache_nested_set" do
|
329
|
+
it "should cache all descendant nodes so that calls to #children or #parent don't hit the database" do
|
330
|
+
@r1c2.cache_nested_set
|
331
|
+
|
332
|
+
@model.delete_all
|
333
|
+
|
334
|
+
@r1c2.children[0].should == @r1c2s1
|
335
|
+
@r1c2.children[1].should == @r1c2s2
|
336
|
+
@r1c2.children[1].children[0].should == @r1c2s2m1
|
337
|
+
@r1c2.children[2].should == @r1c2s3
|
338
|
+
|
339
|
+
@r1c2.children[1].children[0].parent.should == @r1c2s2
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
describe "#parent" do
|
344
|
+
it "should find the parent node" do
|
345
|
+
@r1c1.parent.should == @r1
|
346
|
+
@r1c2s2.parent.should == @r1c2
|
347
|
+
@r1c2s2m1.parent.should == @r1c2s2
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe "#children" do
|
352
|
+
it "should find all nodes that are direct descendants of this one" do
|
353
|
+
@r1.children.should == [@r1c1, @r1c2, @r1c3]
|
354
|
+
@r1c2s2.children.should == [@r1c2s2m1]
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should allow creation of children" do
|
358
|
+
child = @r1c2.children.create!(valid_attributes)
|
359
|
+
|
360
|
+
child.parent_id.should == @r1c2.id
|
361
|
+
child.bounds.should == (15..16)
|
362
|
+
end
|
363
|
+
|
364
|
+
it "should allow addition of children" do
|
365
|
+
@r2.children << @r1c2
|
366
|
+
|
367
|
+
reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
|
368
|
+
|
369
|
+
@r1.bounds.should == (1..8)
|
370
|
+
@r1c3.bounds.should == (6..7)
|
371
|
+
@r2.bounds.should == (9..22)
|
372
|
+
@r1c2.bounds.should == (12..21)
|
373
|
+
@r1c2s1.bounds.should == (13..14)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe "#patriarch" do
|
378
|
+
it "should find the root node that this node descended from" do
|
379
|
+
@r1c1.patriarch.should == @r1
|
380
|
+
@r1c2s2.patriarch.should == @r1
|
381
|
+
@r1c2s2m1.patriarch.should == @r1
|
382
|
+
@r2c1.patriarch.should == @r2
|
383
|
+
@r1.patriarch.should == @r1
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
describe "#root" do
|
388
|
+
it "should find the root node that this node descended from" do
|
389
|
+
@r1c1.root.should == @r1
|
390
|
+
@r1c2s2.root.should == @r1
|
391
|
+
@r1c2s2m1.root.should == @r1
|
392
|
+
@r2c1.root.should == @r2
|
393
|
+
@r1.root.should == @r1
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe "#root?" do
|
398
|
+
it "should be true if node doesn't have a parent" do
|
399
|
+
@r1.should be_root
|
400
|
+
@model.roots.should include(@r1)
|
401
|
+
@r1.parent.should be_nil
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
describe "#descendant_of(other_node)" do
|
406
|
+
it "should be true if other_node is an ancestor of node" do
|
407
|
+
reload_models @r1, @r1c2s2
|
408
|
+
|
409
|
+
@r1c2s2.should be_descendant_of(@r1)
|
410
|
+
@r1c2s2.ancestors.should include(@r1)
|
411
|
+
@r1.descendants.should include(@r1c2s2)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
describe "#generation" do
|
416
|
+
it "should find all nodes in the same generation as this one for a root node" do
|
417
|
+
@r1.generation.should == [@r1, @r2, @r3]
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should find all nodes in the same generation as this one" do
|
421
|
+
@r1c1.generation.should == [@r1c1, @r1c2, @r1c3]
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
describe "#siblings" do
|
426
|
+
it "should find all sibling nodes for a root node" do
|
427
|
+
@r1.siblings.should == [@r2, @r3]
|
428
|
+
end
|
429
|
+
|
430
|
+
it "should find all sibling nodes for a child node" do
|
431
|
+
@r1c1.siblings.should == [@r1c2, @r1c3]
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
describe "#descendants" do
|
436
|
+
it "should find all descendants of this node" do
|
437
|
+
@r1.descendants.should == [@r1c1, @r1c1s1, @r1c2, @r1c2s1, @r1c2s2, @r1c2s2m1, @r1c2s3, @r1c3]
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
describe "#family" do
|
442
|
+
it "should combine self and descendants" do
|
443
|
+
@r1.family.should == [@r1, @r1c1, @r1c1s1, @r1c2, @r1c2s1, @r1c2s2, @r1c2s2m1, @r1c2s3, @r1c3]
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
describe "#family_ids" do
|
448
|
+
it "should find all ids of the node's nested set" do
|
449
|
+
@r1c1.family_ids.should == [@r1c1.id, @r1c1s1.id]
|
450
|
+
@r1c2.family_ids.should == [@r1c2.id, @r1c2s1.id, @r1c2s2.id, @r1c2s2m1.id, @r1c2s3.id]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
describe "#ancestors" do
|
455
|
+
it "should return a node's parent and its parent's parents" do
|
456
|
+
@r1c2s2m1.ancestors.should == [@r1c2s2, @r1c2, @r1]
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
describe "#lineage" do
|
461
|
+
it "should return a node, it's parent and its parent's parents" do
|
462
|
+
@r1c2s2m1.lineage.should == [@r1c2s2m1, @r1c2s2, @r1c2, @r1]
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
describe "#level" do
|
467
|
+
it "should give the depth from the node to its root" do
|
468
|
+
@r1.level.should == 0
|
469
|
+
@r1c2.level.should == 1
|
470
|
+
@r1c2s2.level.should == 2
|
471
|
+
@r1c2s2m1.level.should == 3
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
describe "#kin" do
|
476
|
+
it "should find the patriarch and all its descendants" do
|
477
|
+
@r1c2s2.kin.should == [@r1, @r1c1, @r1c1s1, @r1c2, @r1c2s1, @r1c2s2, @r1c2s2m1, @r1c2s3, @r1c3]
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
end
|
482
|
+
|
483
|
+
describe @model, "with acts_as_nested_set" do
|
484
|
+
|
485
|
+
it "should add a new root node if the parent is not set" do
|
486
|
+
without_changing_the_database do
|
487
|
+
@instance = @model.create!(valid_attributes)
|
488
|
+
@instance.parent_id.should be_nil
|
489
|
+
|
490
|
+
@instance.bounds.should == (1..2)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
it "should add a new root node if the parent is not set and there already are some root nodes" do
|
495
|
+
without_changing_the_database do
|
496
|
+
@model.create!(valid_attributes)
|
497
|
+
@model.create!(valid_attributes)
|
498
|
+
@instance = @model.create!(valid_attributes)
|
499
|
+
@instance.reload
|
500
|
+
|
501
|
+
@instance.parent_id.should be_nil
|
502
|
+
@instance.bounds.should == (5..6)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
it "should append a child node to a parent" do
|
507
|
+
without_changing_the_database do
|
508
|
+
@parent = @model.create!(valid_attributes)
|
509
|
+
@parent.bounds.should == (1..2)
|
510
|
+
|
511
|
+
@instance = @model.create!(valid_attributes(:parent => @parent))
|
512
|
+
|
513
|
+
@parent.reload
|
514
|
+
|
515
|
+
@instance.parent.should == @parent
|
516
|
+
|
517
|
+
@instance.bounds.should == (2..3)
|
518
|
+
@parent.bounds.should == (1..4)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
it "should rollback changes if the save is not successfull for some reason" do
|
523
|
+
without_changing_the_database do
|
524
|
+
@parent = @model.create!(valid_attributes)
|
525
|
+
@parent.bounds.should == (1..2)
|
526
|
+
|
527
|
+
@instance = @model.create(invalid_attributes(:parent => @parent))
|
528
|
+
@instance.should be_a_new_record
|
529
|
+
|
530
|
+
@parent.reload
|
531
|
+
|
532
|
+
@parent.bounds.should == (1..2)
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
it "should append a child node to a parent and shift other nodes out of the way" do
|
537
|
+
without_changing_the_database do
|
538
|
+
@root1 = @model.create!(valid_attributes)
|
539
|
+
@root2 = @model.create!(valid_attributes)
|
540
|
+
|
541
|
+
@root1.bounds.should == (1..2)
|
542
|
+
@root2.bounds.should == (3..4)
|
543
|
+
|
544
|
+
@child1 = @model.create!(valid_attributes(:parent => @root1))
|
545
|
+
reload_models(@root1, @root2)
|
546
|
+
|
547
|
+
@root1.bounds.should == (1..4)
|
548
|
+
@root2.bounds.should == (5..6)
|
549
|
+
@child1.bounds.should == (2..3)
|
550
|
+
|
551
|
+
@child2 = @model.create!(valid_attributes(:parent => @root1))
|
552
|
+
reload_models(@root1, @root2, @child1)
|
553
|
+
|
554
|
+
@root1.bounds.should == (1..6)
|
555
|
+
@root2.bounds.should == (7..8)
|
556
|
+
@child1.bounds.should == (2..3)
|
557
|
+
@child2.bounds.should == (4..5)
|
558
|
+
|
559
|
+
@subchild1 = @model.create!(valid_attributes(:parent => @child2))
|
560
|
+
reload_models(@root1, @root2, @child1, @child2)
|
561
|
+
|
562
|
+
@root1.bounds.should == (1..8)
|
563
|
+
@root2.bounds.should == (9..10)
|
564
|
+
@child1.bounds.should == (2..3)
|
565
|
+
@child2.bounds.should == (4..7)
|
566
|
+
@subchild1.bounds.should == (5..6)
|
567
|
+
|
568
|
+
@subchild2 = @model.create!(valid_attributes(:parent => @child1))
|
569
|
+
reload_models(@root1, @root2, @child1, @child2, @subchild1)
|
570
|
+
|
571
|
+
@root1.bounds.should == (1..10)
|
572
|
+
@root2.bounds.should == (11..12)
|
573
|
+
@child1.bounds.should == (2..5)
|
574
|
+
@child2.bounds.should == (6..9)
|
575
|
+
@subchild1.bounds.should == (7..8)
|
576
|
+
@subchild2.bounds.should == (3..4)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
end
|
581
|
+
|
582
|
+
def reload_models(*attrs)
|
583
|
+
attrs.each {|m| m.reload }
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|