glebtv-mongoid_nested_set 0.3.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.
@@ -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