chrislloyd-eb_nested_set 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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