glebtv-mongoid_nested_set 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,786 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+
4
+ describe Mongoid::Acts::NestedSet do
5
+
6
+ it "provides the acts_as_nested_set method" do
7
+ Node.should respond_to('acts_as_nested_set')
8
+ NodeWithoutNestedSet.should respond_to('acts_as_nested_set')
9
+ end
10
+
11
+ end
12
+
13
+
14
+ describe "A Mongoid::Document" do
15
+
16
+ def create_clothing_nodes(klass=Node)
17
+ nodes = {}
18
+ # See Wikipedia for an illustration of the first tree
19
+ # http://en.wikipedia.org/wiki/Nested_set_model#Example
20
+ nodes[:clothing] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Clothing', 'lft' => 1, 'rgt' => 22, 'depth' => 0, 'number' => nil, 'parent_id' => nil)
21
+ nodes[:mens] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Men\'s', 'lft' => 2, 'rgt' => 9, 'depth' => 1, 'number' => '1', 'parent_id' => nodes[:clothing].id)
22
+ nodes[:suits] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Suits', 'lft' => 3, 'rgt' => 8, 'depth' => 2, 'number' => '1.1', 'parent_id' => nodes[:mens].id)
23
+ nodes[:slacks] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Slacks', 'lft' => 4, 'rgt' => 5, 'depth' => 3, 'number' => '1.1.1', 'parent_id' => nodes[:suits].id)
24
+ nodes[:jackets] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Jackets', 'lft' => 6, 'rgt' => 7, 'depth' => 3, 'number' => '1.1.2', 'parent_id' => nodes[:suits].id)
25
+ nodes[:womens] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Women\'s', 'lft' => 10, 'rgt' => 21, 'depth' => 1, 'number' => '2', 'parent_id' => nodes[:clothing].id)
26
+ nodes[:dresses] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Dresses', 'lft' => 11, 'rgt' => 16, 'depth' => 2, 'number' => '2.1', 'parent_id' => nodes[:womens].id)
27
+ nodes[:skirts] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Skirts', 'lft' => 17, 'rgt' => 18, 'depth' => 2, 'number' => '2.2', 'parent_id' => nodes[:womens].id)
28
+ nodes[:blouses] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Blouses', 'lft' => 19, 'rgt' => 20, 'depth' => 2, 'number' => '2.3', 'parent_id' => nodes[:womens].id)
29
+ nodes[:gowns] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Gowns', 'lft' => 12, 'rgt' => 13, 'depth' => 3, 'number' => '2.1.1', 'parent_id' => nodes[:dresses].id)
30
+ nodes[:sundress] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Sun Dresses', 'lft' => 14, 'rgt' => 15, 'depth' => 3, 'number' => '2.1.2', 'parent_id' => nodes[:dresses].id)
31
+ nodes
32
+ end
33
+
34
+ def create_electronics_nodes(klass=Node)
35
+ nodes = {}
36
+ # See MySQL for an illustration of the second tree
37
+ # http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
38
+ nodes[:electronics] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Electronics', 'lft' => 1, 'rgt' => 20, 'depth' => 0, 'number' => nil, 'parent_id' => nil)
39
+ nodes[:televisions] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Televisions', 'lft' => 2, 'rgt' => 9, 'depth' => 1, 'number' => '1', 'parent_id' => nodes[:electronics].id)
40
+ nodes[:tube] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Tube', 'lft' => 3, 'rgt' => 4, 'depth' => 2, 'number' => '1.1', 'parent_id' => nodes[:televisions].id)
41
+ nodes[:lcd] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'LCD', 'lft' => 5, 'rgt' => 6, 'depth' => 2, 'number' => '1.2', 'parent_id' => nodes[:televisions].id)
42
+ nodes[:plasma] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Plasma', 'lft' => 7, 'rgt' => 8, 'depth' => 2, 'number' => '1.3', 'parent_id' => nodes[:televisions].id)
43
+ nodes[:portable] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Portable', 'lft' => 10, 'rgt' => 19, 'depth' => 1, 'number' => '2', 'parent_id' => nodes[:electronics].id)
44
+ nodes[:mp3] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'MP3', 'lft' => 11, 'rgt' => 14, 'depth' => 2, 'number' => '2.1', 'parent_id' => nodes[:portable].id)
45
+ nodes[:cd] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'CD', 'lft' => 15, 'rgt' => 16, 'depth' => 2, 'number' => '2.2', 'parent_id' => nodes[:portable].id)
46
+ nodes[:radio] = klass.new.test_set_attributes('root_id' => 2, 'name' => '2 Way Radio', 'lft' => 17, 'rgt' => 18, 'depth' => 2, 'number' => '2.3', 'parent_id' => nodes[:portable].id)
47
+ nodes[:flash] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Flash', 'lft' => 12, 'rgt' => 13, 'depth' => 3, 'number' => '2.1.1', 'parent_id' => nodes[:mp3].id)
48
+ nodes
49
+ end
50
+
51
+ def persist_nodes(nodes, collection_name=nil)
52
+ nodes = {:first => nodes} unless nodes.is_a? Hash
53
+ collection_name = nodes.values.first.class.collection_name if collection_name.nil?
54
+
55
+ nodes.each_value do |node|
56
+ # As soon as there is no upsert callbacks set
57
+ # this is effectively identical to the straight driver call
58
+ node.with(:conllection => collection_name).upsert
59
+ node.new_record = false
60
+ end
61
+ nodes
62
+ end
63
+
64
+
65
+
66
+
67
+ context "that does not act as a nested set" do
68
+ it "does not have a left field" do
69
+ NodeWithoutNestedSet.should_not have_field('lft', :type => Integer)
70
+ end
71
+
72
+ it "does not have a right field" do
73
+ NodeWithoutNestedSet.should_not have_field('rgt', :type => Integer)
74
+ end
75
+
76
+ it "does not include NestedSet methods" do
77
+ NodeWithoutNestedSet.should_not respond_to('descendant_of')
78
+ NodeWithoutNestedSet.new.should_not respond_to('left')
79
+ NodeWithoutNestedSet.should_not respond_to('each_with_outline_number')
80
+ end
81
+ end
82
+
83
+
84
+ context "that acts as an un-scoped nested set" do
85
+
86
+ context "in a tree" do
87
+ before(:each) do
88
+ @nodes = persist_nodes(create_clothing_nodes(UnscopedNode))
89
+ end
90
+
91
+ it "can detect if roots are valid" do
92
+ UnscopedNode.should be_all_roots_valid
93
+
94
+ persist_nodes(UnscopedNode.new(:name => 'Test').test_set_attributes(:lft => 20, :rgt => 30, :parent_id=>nil))
95
+ UnscopedNode.should_not be_all_roots_valid
96
+ end
97
+
98
+ it "can detect if left and rights are valid" do
99
+ UnscopedNode.should be_left_and_rights_valid
100
+
101
+ # left > right
102
+ n = UnscopedNode.new(:name => 'Test').test_set_attributes(:lft => 6, :rgt => 5, :parent_id=>@nodes[:suits].id)
103
+ persist_nodes(n)
104
+ UnscopedNode.should_not be_left_and_rights_valid
105
+
106
+ # left == right
107
+ persist_nodes(n.test_set_attributes(:rgt => 6))
108
+ UnscopedNode.should_not be_left_and_rights_valid
109
+
110
+ # Overlaps parent
111
+ persist_nodes(n.test_set_attributes(:rgt => 8))
112
+ UnscopedNode.should_not be_left_and_rights_valid
113
+ end
114
+
115
+ it "can detect duplicate left and right values" do
116
+ UnscopedNode.should be_no_duplicates_for_fields
117
+
118
+ n = UnscopedNode.new(:name => 'Test').test_set_attributes(:lft => 6, :rgt => 25, :parent_id=>@nodes[:suits].id)
119
+ persist_nodes(n)
120
+ UnscopedNode.should_not be_no_duplicates_for_fields
121
+
122
+ persist_nodes(n.test_set_attributes(:lft => 5, :rgt => 7, :parent_id=>@nodes[:suits].id))
123
+ UnscopedNode.should_not be_no_duplicates_for_fields
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ context "that acts as a scoped nested set" do
130
+
131
+ it "does not include outline number methods" do
132
+ Node.should_not respond_to('each_with_outline_number')
133
+ end
134
+
135
+ # Adds fields
136
+
137
+ it "has a left field" do
138
+ Node.should have_field('lft', :type => Integer)
139
+ RenamedFields.should have_field('red', :type => Integer)
140
+ RenamedFields.should_not have_field('lft', :type => Integer)
141
+ end
142
+
143
+ it "has a right field" do
144
+ Node.should have_field('rgt', :type => Integer)
145
+ RenamedFields.should have_field('red', :type => Integer)
146
+ RenamedFields.should_not have_field('rgt', :type => Integer)
147
+ end
148
+
149
+ it "has a parent field" do
150
+ # Starting in Mongoid 2.0.rc1, all foreign keys are Objects
151
+ Node.should have_field('parent_id', :type => Object)
152
+ RenamedFields.should have_field('mother_id', :type => Object)
153
+ RenamedFields.should_not have_field('parent_id', :type => Object)
154
+ end
155
+
156
+ it "does not have a number field" do
157
+ Node.should_not have_field('number', :type => String)
158
+ end
159
+
160
+ it "has a default left field name" do
161
+ Node.acts_as_nested_set_options[:left_field].should == 'lft'
162
+ end
163
+
164
+ it "has a default right field name" do
165
+ Node.acts_as_nested_set_options[:right_field].should == 'rgt'
166
+ end
167
+
168
+ it "has a default parent field name" do
169
+ Node.acts_as_nested_set_options[:parent_field].should == 'parent_id'
170
+ end
171
+
172
+ it "returns the left field name" do
173
+ Node.left_field_name.should == 'lft'
174
+ Node.new.left_field_name.should == 'lft'
175
+ RenamedFields.left_field_name.should == 'red'
176
+ RenamedFields.new.left_field_name.should == 'red'
177
+ end
178
+
179
+ it "returns the right field name" do
180
+ Node.right_field_name.should == 'rgt'
181
+ Node.new.right_field_name.should == 'rgt'
182
+ RenamedFields.right_field_name.should == 'black'
183
+ RenamedFields.new.right_field_name.should == 'black'
184
+ end
185
+
186
+ it "returns the parent field name" do
187
+ Node.parent_field_name.should == 'parent_id'
188
+ Node.new.parent_field_name.should == 'parent_id'
189
+ RenamedFields.parent_field_name.should == 'mother_id'
190
+ RenamedFields.new.parent_field_name.should == 'mother_id'
191
+ end
192
+
193
+ it "does not allow assigning the left field" do
194
+ expect { Node.new.lft = 1 }.to raise_error(NameError)
195
+ expect { RenamedFields.new.red = 1 }.to raise_error(NameError)
196
+ end
197
+
198
+ it "does not allow assigning the right field" do
199
+ expect { Node.new.rgt = 1 }.to raise_error(NameError)
200
+ expect { RenamedFields.new.black = 1 }.to raise_error(NameError)
201
+ end
202
+
203
+
204
+
205
+
206
+ # No-Database Calculations
207
+
208
+ context "with other nodes" do
209
+ before(:each) do
210
+ @nodes = create_clothing_nodes.merge(create_electronics_nodes)
211
+ end
212
+
213
+ it "determines if it is a root node" do
214
+ @nodes[:mens].should_not be_root
215
+ @nodes[:clothing].should be_root
216
+ end
217
+
218
+ it "determines if it is a leaf node" do
219
+ @nodes[:suits].should_not be_leaf
220
+ @nodes[:jackets].should be_leaf
221
+ end
222
+
223
+ it "determines if it is a child node" do
224
+ @nodes[:mens].should be_child
225
+ @nodes[:clothing].should_not be_child
226
+ end
227
+
228
+ it "determines if it is a descendant of another node" do
229
+ @nodes[:sundress].should be_descendant_of(@nodes[:dresses])
230
+ @nodes[:dresses].should_not be_descendant_of(@nodes[:sundress])
231
+ @nodes[:dresses].should_not be_descendant_of(@nodes[:dresses])
232
+ @nodes[:flash].should_not be_descendant_of(@nodes[:dresses])
233
+ end
234
+
235
+ it "determines if it is a descendant of or equal to another node" do
236
+ @nodes[:sundress].should be_is_or_is_descendant_of(@nodes[:dresses])
237
+ @nodes[:sundress].should be_is_or_is_descendant_of(@nodes[:sundress])
238
+ @nodes[:dresses].should_not be_is_or_is_descendant_of(@nodes[:sundress])
239
+ @nodes[:flash].should_not be_is_or_is_descendant_of(@nodes[:dresses])
240
+ @nodes[:skirts].should_not be_is_or_is_descendant_of(@nodes[:radio])
241
+ end
242
+
243
+ it "determines if it is an ancestor of another node" do
244
+ @nodes[:suits].should be_ancestor_of(@nodes[:jackets])
245
+ @nodes[:jackets].should_not be_ancestor_of(@nodes[:suits])
246
+ @nodes[:suits].should_not be_ancestor_of(@nodes[:suits])
247
+ @nodes[:dresses].should_not be_ancestor_of(@nodes[:flash])
248
+ end
249
+
250
+ it "determines if it is an ancestor of or equal to another node" do
251
+ @nodes[:suits].should be_is_or_is_ancestor_of(@nodes[:jackets])
252
+ @nodes[:suits].should be_is_or_is_ancestor_of(@nodes[:suits])
253
+ @nodes[:jackets].should_not be_is_or_is_ancestor_of(@nodes[:suits])
254
+ @nodes[:dresses].should_not be_is_or_is_ancestor_of(@nodes[:flash])
255
+ @nodes[:radio].should_not be_is_or_is_ancestor_of(@nodes[:skirts])
256
+ end
257
+
258
+ end
259
+
260
+
261
+ context "in an empty tree" do
262
+
263
+ it "can create a root node" do
264
+ root = Node.create(:name => 'Root Category')
265
+ root.should have_nestedset_pos(1, 2)
266
+ root.depth.should == 0
267
+ end
268
+
269
+ it "can create a child node via children.create" do
270
+ root = Node.create(:name => 'Root Category')
271
+ child = root.children.create(:name => 'Child Category')
272
+ child.should have_nestedset_pos(2, 3)
273
+ child.parent_id.should == root.id
274
+ child.depth.should == 1
275
+ root.reload.should have_nestedset_pos(1, 4)
276
+ root.depth.should == 0
277
+ end
278
+
279
+ it "can create a child node via children<<" do
280
+ root = Node.create(:name => 'Root Category')
281
+ child = Node.create(:name => 'Child Category')
282
+ root.children << child
283
+ child.parent_id.should == root.id
284
+ child.should have_nestedset_pos(2, 3)
285
+ child.depth.should == 1
286
+ root.reload.should have_nestedset_pos(1, 4)
287
+ root.depth.should == 0
288
+ end
289
+
290
+ it "can create 2 level child nodes via children<<" do
291
+ root = Node.create(:name => 'Root Category', :root_id => 10)
292
+ child = Node.create(:name => 'Child Category', :root_id => 10)
293
+ grandchild = Node.create(:name => 'Grandchild Category', :root_id => 10)
294
+ root.children << child
295
+ child.children << grandchild
296
+ grandchild.parent_id.should == child.id
297
+ grandchild.reload.should have_nestedset_pos(3, 4)
298
+ grandchild.depth.should == 2
299
+ child.parent_id.should == root.id
300
+ child.reload.should have_nestedset_pos(2, 5)
301
+ child.depth.should == 1
302
+ root.reload.should have_nestedset_pos(1, 6)
303
+ root.depth.should == 0
304
+ end
305
+
306
+ it "can create a child node with parent pre-assigned" do
307
+ root = Node.create(:name => 'Root Category')
308
+ child = Node.create(:name => 'Child Category', :parent => root)
309
+ child.should have_nestedset_pos(2, 3)
310
+ child.parent_id.should == root.id
311
+ child.depth.should == 1
312
+ root.reload.should have_nestedset_pos(1, 4)
313
+ root.depth.should == 0
314
+ end
315
+
316
+ it "can create a child node with parent id pre-assigned" do
317
+ root = Node.create(:name => 'Root Category')
318
+ child = Node.create(:name => 'Child Category', :parent_id => root.id)
319
+ child.should have_nestedset_pos(2, 3)
320
+ child.parent_id.should == root.id
321
+ child.depth.should == 1
322
+ root.reload.should have_nestedset_pos(1, 4)
323
+ root.depth.should == 0
324
+ end
325
+
326
+ it "can change a new node's parent before saving" do
327
+ root = Node.create(:name => 'Root Category')
328
+ child = Node.new(:name => 'Child Category')
329
+ child.parent = root
330
+ child.save
331
+ child.should have_nestedset_pos(2, 3)
332
+ child.parent_id.should == root.id
333
+ child.depth.should == 1
334
+ root.reload.should have_nestedset_pos(1, 4)
335
+ root.depth.should == 0
336
+ end
337
+
338
+ it "can change a new node's parent id before saving" do
339
+ root = Node.create(:name => 'Root Category')
340
+ child = Node.new(:name => 'Child Category')
341
+ child.parent_id = root.id
342
+ child.save
343
+ child.should have_nestedset_pos(2, 3)
344
+ child.parent_id.should == root.id
345
+ child.depth.should == 1
346
+ root.reload.should have_nestedset_pos(1, 4)
347
+ root.depth.should == 0
348
+ end
349
+
350
+ end
351
+
352
+
353
+ context "in a tree" do
354
+
355
+ before(:each) do
356
+ @nodes = persist_nodes(create_clothing_nodes.merge(create_electronics_nodes))
357
+ end
358
+
359
+
360
+ # Scopes
361
+
362
+ it "fetches all root nodes" do
363
+ Node.roots.should have(2).entries
364
+ end
365
+
366
+ it "fetches all leaf nodes in order" do
367
+ Node.leaves.where(:root_id=>1).map {|e| e.name}.should == %w[Slacks Jackets Gowns Sun\ Dresses Skirts Blouses]
368
+ end
369
+
370
+ it "fetches all nodes with a given depth in order" do
371
+ Node.with_depth(1).where(:root_id=>1).map {|e| e.name}.should == %w[Men's Women's]
372
+ end
373
+
374
+
375
+ # Queries
376
+
377
+ it "fetches descendants of multiple parents" do
378
+ parents = Node.any_in(:name => %w[Men's Dresses])
379
+ Node.where(:root_id=>1).descendants_of(parents).should have(5).entries
380
+ end
381
+
382
+ it "fetches self and ancestors in order" do
383
+ @nodes[:dresses].self_and_ancestors.map {|e| e.name}.should == %w[Clothing Women's Dresses]
384
+ end
385
+
386
+ it "fetches ancestors in order" do
387
+ @nodes[:dresses].ancestors.map {|e| e.name}.should == %w[Clothing Women's]
388
+ end
389
+
390
+ it "fetches its root" do
391
+ @nodes[:dresses].root.name.should == 'Clothing'
392
+ end
393
+
394
+ it "fetches self and siblings in order" do
395
+ @nodes[:skirts].self_and_siblings.map {|e| e.name}.should == %w[Dresses Skirts Blouses]
396
+ end
397
+
398
+ it "fetches siblings in order" do
399
+ @nodes[:skirts].siblings.map {|e| e.name}.should == %w[Dresses Blouses]
400
+ end
401
+
402
+ it "fetches leaves in order" do
403
+ @nodes[:womens].leaves.map {|e| e.name}.should == %w[Gowns Sun\ Dresses Skirts Blouses]
404
+ end
405
+
406
+ it "fetches its current level" do
407
+ @nodes[:suits].level.should == 2
408
+ end
409
+
410
+ it "fetches self and descendants in order" do
411
+ @nodes[:womens].self_and_descendants.map {|e| e.name}.should == %w[Women's Dresses Gowns Sun\ Dresses Skirts Blouses]
412
+ end
413
+
414
+ it "fetches descendants in order" do
415
+ @nodes[:womens].descendants.map {|e| e.name}.should == %w[Dresses Gowns Sun\ Dresses Skirts Blouses]
416
+ end
417
+
418
+ it "fetches its first sibling to the left" do
419
+ @nodes[:skirts].left_sibling.name.should == 'Dresses'
420
+ @nodes[:slacks].left_sibling.should == nil
421
+ end
422
+
423
+ it "fetches its first sibling to the right" do
424
+ @nodes[:skirts].right_sibling.name.should == 'Blouses'
425
+ @nodes[:jackets].right_sibling.should == nil
426
+ end
427
+
428
+ it "can detect if roots are valid" do
429
+ Node.should be_all_roots_valid
430
+
431
+ persist_nodes(Node.new(:name => 'Test').test_set_attributes(:root_id => 1, :lft => 20, :rgt => 30, :parent_id=>nil))
432
+ Node.should_not be_all_roots_valid
433
+ end
434
+
435
+ it "can detect if left and rights are valid" do
436
+ Node.should be_left_and_rights_valid
437
+
438
+ # left > right
439
+ n = Node.new(:name => 'Test').test_set_attributes(:root_id => 1, :lft => 6, :rgt => 5, :parent_id=>@nodes[:suits].id)
440
+ persist_nodes(n)
441
+ Node.should_not be_left_and_rights_valid
442
+
443
+ # left == right
444
+ persist_nodes(n.test_set_attributes(:rgt => 6))
445
+ Node.should_not be_left_and_rights_valid
446
+
447
+ # Overlaps parent
448
+ persist_nodes(n.test_set_attributes(:rgt => 8))
449
+ Node.should_not be_left_and_rights_valid
450
+ end
451
+
452
+ it "can detect duplicate left and right values" do
453
+ Node.should be_no_duplicates_for_fields
454
+
455
+ n = Node.new(:name => 'Test').test_set_attributes(:root_id => 1, :lft => 6, :rgt => 25, :parent_id=>@nodes[:suits].id)
456
+ persist_nodes(n)
457
+ Node.should_not be_no_duplicates_for_fields
458
+
459
+ persist_nodes(n.test_set_attributes(:lft => 5, :rgt => 7, :parent_id=>@nodes[:suits].id))
460
+ Node.should_not be_no_duplicates_for_fields
461
+ end
462
+
463
+
464
+ # Moves
465
+
466
+ it "cannot move a new node" do
467
+ n = Node.new(:name => 'Test', :root_id => 1)
468
+ expect {
469
+ n.move_to_right_of(Node.where(:name => 'Jackets').first)
470
+ }.to raise_error(Mongoid::Errors::MongoidError, /move.*new node/)
471
+ end
472
+
473
+ it "cannot move a node inside its tree" do
474
+ n = Node.where(:name => 'Men\'s').first
475
+ expect {
476
+ n.move_to_right_of(Node.where(:name => 'Suits').first)
477
+ }.to raise_error(Mongoid::Errors::MongoidError, /possible/)
478
+ end
479
+
480
+ it "cannot move a node to a non-existent target" do
481
+ @nodes[:mens].parent_id = Moped::BSON::ObjectId.new
482
+ expect {
483
+ @nodes[:mens].save
484
+ }.to raise_error(Mongoid::Errors::MongoidError, /possible.*(exist|found)/)
485
+ end
486
+
487
+ it "adds newly created nodes to the end of the tree" do
488
+ Node.create(:name => 'Vests', :root_id => 1).should have_nestedset_pos(23, 24)
489
+
490
+ n = Node.new(:name => 'Test', :root_id => 1)
491
+ n.save
492
+ n.should have_nestedset_pos(25, 26)
493
+ end
494
+
495
+ it "can move left" do
496
+ @nodes[:jackets].move_left
497
+ @nodes[:jackets] .should have_nestedset_pos( 4, 5)
498
+ @nodes[:slacks].reload.should have_nestedset_pos( 6, 7)
499
+ @nodes[:suits] .reload.should have_nestedset_pos( 3, 8)
500
+ @nodes[:jackets].depth.should == 3
501
+ @nodes[:slacks].depth.should == 3
502
+ @nodes[:suits].depth.should == 2
503
+ end
504
+
505
+ it "can move right" do
506
+ @nodes[:slacks].move_right
507
+ @nodes[:slacks] .should have_nestedset_pos( 6, 7)
508
+ @nodes[:jackets].reload.should have_nestedset_pos( 4, 5)
509
+ @nodes[:suits] .reload.should have_nestedset_pos( 3, 8)
510
+ @nodes[:slacks].depth.should == 3
511
+ @nodes[:jackets].depth.should == 3
512
+ @nodes[:suits].depth.should == 2
513
+ end
514
+
515
+ it "can move left of another node" do
516
+ @nodes[:slacks].move_to_left_of(@nodes[:skirts])
517
+ @nodes[:slacks] .should have_nestedset_pos(15, 16)
518
+ @nodes[:skirts] .should have_nestedset_pos(17, 18)
519
+ @nodes[:skirts] .reload.should have_nestedset_pos(17, 18)
520
+ @nodes[:dresses].reload.should have_nestedset_pos( 9, 14)
521
+ @nodes[:womens] .reload.should have_nestedset_pos( 8, 21)
522
+ @nodes[:slacks].depth.should == 2
523
+ end
524
+
525
+ it "can move right of another node" do
526
+ @nodes[:slacks].move_to_right_of(@nodes[:skirts])
527
+ @nodes[:slacks] .should have_nestedset_pos(17, 18)
528
+ @nodes[:skirts] .should have_nestedset_pos(15, 16)
529
+ @nodes[:skirts] .reload.should have_nestedset_pos(15, 16)
530
+ @nodes[:blouses].reload.should have_nestedset_pos(19, 20)
531
+ @nodes[:womens] .reload.should have_nestedset_pos( 8, 21)
532
+ @nodes[:slacks].depth.should == 2
533
+ end
534
+
535
+ it "can move as a child of another node" do
536
+ @nodes[:slacks].move_to_child_of(@nodes[:dresses])
537
+ @nodes[:slacks] .should have_nestedset_pos(14, 15)
538
+ @nodes[:dresses] .should have_nestedset_pos( 9, 16)
539
+ @nodes[:dresses].reload.should have_nestedset_pos( 9, 16)
540
+ @nodes[:gowns] .reload.should have_nestedset_pos(10, 11)
541
+ @nodes[:mens] .reload.should have_nestedset_pos( 2, 7)
542
+ @nodes[:slacks].depth.should == 3
543
+ end
544
+
545
+ it "can change it's parent id" do
546
+ @nodes[:slacks].parent_id = @nodes[:dresses].id
547
+ @nodes[:slacks].save
548
+ @nodes[:slacks] .reload.should have_nestedset_pos(14, 15)
549
+ @nodes[:dresses].reload.should have_nestedset_pos( 9, 16)
550
+ @nodes[:gowns] .reload.should have_nestedset_pos(10, 11)
551
+ @nodes[:mens] .reload.should have_nestedset_pos( 2, 7)
552
+ @nodes[:slacks].depth.should == 3
553
+ end
554
+
555
+ it "can move to the root position" do
556
+ @nodes[:suits].move_to_root
557
+ @nodes[:suits] .should be_root
558
+ @nodes[:suits] .should have_nestedset_pos( 1, 6)
559
+ @nodes[:jackets] .reload.should have_nestedset_pos( 4, 5)
560
+ @nodes[:clothing].reload.should have_nestedset_pos( 7, 22)
561
+ @nodes[:mens] .reload.should have_nestedset_pos( 8, 9)
562
+ @nodes[:womens] .reload.should have_nestedset_pos(10, 21)
563
+ end
564
+
565
+ it "can move to the left of root" do
566
+ @nodes[:suits].move_to_left_of(@nodes[:clothing])
567
+ @nodes[:suits] .should be_root
568
+ @nodes[:suits] .should have_nestedset_pos( 1, 6)
569
+ @nodes[:jackets] .reload.should have_nestedset_pos( 4, 5)
570
+ @nodes[:clothing].reload.should have_nestedset_pos( 7, 22)
571
+ @nodes[:mens] .reload.should have_nestedset_pos( 8, 9)
572
+ @nodes[:womens] .reload.should have_nestedset_pos(10, 21)
573
+ end
574
+
575
+ it "can move to the right of root" do
576
+ @nodes[:suits].move_to_right_of(@nodes[:clothing])
577
+ @nodes[:suits] .should be_root
578
+ @nodes[:suits] .should have_nestedset_pos(17, 22)
579
+ @nodes[:jackets] .reload.should have_nestedset_pos(20, 21)
580
+ @nodes[:clothing].reload.should have_nestedset_pos( 1, 16)
581
+ @nodes[:mens] .reload.should have_nestedset_pos( 2, 3)
582
+ @nodes[:womens] .reload.should have_nestedset_pos( 4, 15)
583
+ end
584
+
585
+ it "can move node with children" do
586
+ @nodes[:suits].move_to_child_of(@nodes[:dresses])
587
+ @nodes[:suits] .should have_nestedset_pos(10, 15)
588
+ @nodes[:dresses] .should have_nestedset_pos( 5, 16)
589
+ @nodes[:mens] .reload.should have_nestedset_pos( 2, 3)
590
+ @nodes[:womens] .reload.should have_nestedset_pos( 4, 21)
591
+ @nodes[:sundress].reload.should have_nestedset_pos( 8, 9)
592
+ @nodes[:jackets] .reload.should have_nestedset_pos(13, 14)
593
+ @nodes[:suits].depth.should == 3
594
+ @nodes[:jackets].depth.should == 4
595
+ end
596
+
597
+ it "can loop over elements starting at root with level" do
598
+ i = 0
599
+ Node.each_with_level(@nodes[:clothing].self_and_descendants) do |o, level|
600
+ level.should == o.depth
601
+ i += 1
602
+ end
603
+ i.should == 11
604
+ end
605
+
606
+ it "can loop over elements starting at non-root with level" do
607
+ i = 0
608
+ Node.each_with_level(@nodes[:mens].self_and_descendants) do |o, level|
609
+ level.should == o.depth
610
+ i += 1
611
+ end
612
+ i.should == 4
613
+ end
614
+
615
+ it "can loop over elements starting at root with ancestors" do
616
+ i = 0
617
+ Node.each_with_ancestors(@nodes[:clothing].self_and_descendants) do |o, ancestors|
618
+ ancestors.should == o.ancestors.entries
619
+ i += 1
620
+ end
621
+ i.should == 11
622
+ end
623
+
624
+ it "can loop over elements starting at non-root with ancestors" do
625
+ i = 0
626
+ Node.each_with_ancestors(@nodes[:mens].self_and_descendants) do |o, ancestors|
627
+ ancestors.should == o.ancestors.entries
628
+ i += 1
629
+ end
630
+ i.should == 4
631
+ end
632
+
633
+ context "with dependent=delete_all" do
634
+ it "deletes descendants when destroyed" do
635
+ @nodes[:mens].destroy
636
+ @nodes[:clothing].reload.should have_nestedset_pos( 1, 14)
637
+ @nodes[:womens] .reload.should have_nestedset_pos( 2, 13)
638
+ Node.where(:name => 'Men\'s').count.should == 0
639
+ Node.where(:name => 'Suits').count.should == 0
640
+ Node.where(:name => 'Slacks').count.should == 0
641
+ end
642
+ end
643
+
644
+ context "with dependent=destroy" do
645
+ it "deletes descendants when destroyed" do
646
+ Node.test_set_dependent_option :destroy
647
+ @nodes[:mens].destroy
648
+ @nodes[:clothing].reload.should have_nestedset_pos( 1, 14)
649
+ @nodes[:womens] .reload.should have_nestedset_pos( 2, 13)
650
+ Node.where(:name => 'Men\'s').count.should == 0
651
+ Node.where(:name => 'Suits').count.should == 0
652
+ Node.where(:name => 'Slacks').count.should == 0
653
+ end
654
+ end
655
+
656
+ end
657
+
658
+
659
+ context "in an adjaceny list tree" do
660
+ before(:each) do
661
+ @nodes = create_clothing_nodes(Node)
662
+ @nodes.each_value { |node| node.test_set_attributes(:rgt => nil) }
663
+ persist_nodes(@nodes)
664
+ end
665
+
666
+ it "can rebuild nested set properties" do
667
+ Node.rebuild!
668
+ root = Node.root
669
+ root.should be_a(Node)
670
+ root.name.should == 'Clothing'
671
+
672
+ @nodes[:clothing].reload.should have_nestedset_pos( 1, 22)
673
+ @nodes[:mens] .reload.should have_nestedset_pos( 2, 9)
674
+ @nodes[:womens] .reload.should have_nestedset_pos(10, 21)
675
+ @nodes[:suits] .reload.should have_nestedset_pos( 3, 8)
676
+ @nodes[:skirts] .reload.should have_nestedset_pos(17, 18)
677
+ end
678
+
679
+ end
680
+ end
681
+
682
+
683
+ context "that acts as a nested set with inheritance" do
684
+ def create_shape_nodes
685
+ nodes = {}
686
+ nodes[:root] = SquareNode.new.test_set_attributes('name' => 'Root', 'lft' => 1, 'rgt' => 12, 'depth' => 0, 'parent_id' => nil)
687
+ nodes[:c1] = SquareNode.new.test_set_attributes('name' => '1', 'lft' => 2, 'rgt' => 7, 'depth' => 1, 'parent_id' => nodes[:root].id)
688
+ nodes[:c2] = SquareNode.new.test_set_attributes('name' => '2', 'lft' => 8, 'rgt' => 9, 'depth' => 1, 'parent_id' => nodes[:root].id)
689
+ nodes[:c3] = CircleNode.new.test_set_attributes('name' => '3', 'lft' => 10, 'rgt' => 11, 'depth' => 1, 'parent_id' => nodes[:root].id)
690
+ nodes[:c11] = CircleNode.new.test_set_attributes('name' => '1.1', 'lft' => 3, 'rgt' => 4, 'depth' => 2, 'parent_id' => nodes[:c1].id)
691
+ nodes[:c12] = SquareNode.new.test_set_attributes('name' => '1.2', 'lft' => 5, 'rgt' => 6, 'depth' => 2, 'parent_id' => nodes[:c1].id)
692
+ nodes
693
+ end
694
+
695
+ context "in a tree" do
696
+ before(:each) do
697
+ @nodes = create_shape_nodes
698
+ persist_nodes(@nodes)
699
+ end
700
+
701
+ it "fetches self and descendants in order" do
702
+ @nodes[:root].self_and_descendants.map {|e| e.name}.should == %w[Root 1 1.1 1.2 2 3]
703
+ end
704
+ end
705
+ end
706
+
707
+
708
+ context "that acts as a nested set with outline numbering" do
709
+
710
+ it "includes outline number methods" do
711
+ NumberingNode.should respond_to('each_with_outline_number')
712
+ end
713
+
714
+ it "has a number field" do
715
+ NumberingNode.should have_field('number', :type => String)
716
+ end
717
+
718
+ context "in a tree" do
719
+ before(:each) do
720
+ @nodes = persist_nodes(create_clothing_nodes(NumberingNode).merge(create_electronics_nodes(NumberingNode)))
721
+ end
722
+
723
+ it "sets the number for new child nodes" do
724
+ n = NumberingNode.create(:name => 'Vests', :root_id => 1, :parent_id => @nodes[:suits].id)
725
+ n.number.should == '1.1.3'
726
+ end
727
+
728
+ it "updates the number for nodes moved within the same parent" do
729
+ @nodes[:slacks].move_right
730
+ @nodes[:slacks] .number.should == '1.1.2'
731
+ @nodes[:jackets].reload.number.should == '1.1.1'
732
+ end
733
+
734
+ it "updates the number for nodes moved to a new parent" do
735
+ @nodes[:slacks].move_to_child_of(@nodes[:dresses])
736
+ @nodes[:slacks].number.should == '2.1.3'
737
+ end
738
+
739
+ it "updates the number for nodes moved to root" do
740
+ @nodes[:suits].move_to_root
741
+ @nodes[:suits] .number.should be_nil
742
+ @nodes[:suits] .reload.number.should be_nil
743
+ @nodes[:jackets].reload.number.should == '2'
744
+ @nodes[:skirts] .reload.number.should == '2.2'
745
+ end
746
+
747
+ it "updates the number for old siblings of moved nodes" do
748
+ @nodes[:slacks].move_to_child_of(@nodes[:dresses])
749
+ @nodes[:jackets].reload.number.should == '1.1.1'
750
+ end
751
+
752
+ it "updates the number for new siblings of moved nodes" do
753
+ @nodes[:slacks].move_to_left_of(@nodes[:gowns])
754
+ @nodes[:gowns].reload.number.should == '2.1.2'
755
+ end
756
+
757
+ it "updates the number for descendants of moved nodes" do
758
+ @nodes[:suits].move_to_child_of(@nodes[:dresses])
759
+ @nodes[:suits] .number.should == '2.1.3'
760
+ @nodes[:jackets].reload.number.should == '2.1.3.2'
761
+ end
762
+
763
+ it "updates the number for descendants of old siblings of moved nodes" do
764
+ @nodes[:mens].move_to_child_of(@nodes[:womens])
765
+ @nodes[:womens] .reload.number.should == '1'
766
+ @nodes[:dresses].reload.number.should == '1.1'
767
+ end
768
+
769
+ it "updates the number for descendants of new siblings of moved nodes" do
770
+ @nodes[:dresses].move_to_left_of(@nodes[:suits])
771
+ @nodes[:jackets].reload.number == '1.2.2'
772
+ end
773
+
774
+ it "updates the number for a single node" do
775
+ @nodes[:suits].update_attributes(NumberingNode.outline_number_field_name => '3.1')
776
+ @nodes[:suits].number.should == '3.1'
777
+ @nodes[:suits].update_outline_number
778
+ @nodes[:suits].number.should == '1.1'
779
+ end
780
+
781
+
782
+ end
783
+
784
+ end
785
+
786
+ end