mongoid-tree-rational 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Tree::Traversal do
4
+
5
+ subject { OrderedNode }
6
+
7
+ describe '#traverse' do
8
+
9
+ subject { Node.new }
10
+
11
+ [:depth_first, :breadth_first].each do |method|
12
+ it "should support #{method} traversal" do
13
+ expect { subject.traverse(method) {} }.to_not raise_error
14
+ end
15
+ end
16
+
17
+ it "should complain about unsupported traversal methods" do
18
+ expect { subject.traverse('non_existing') {} }.to raise_error
19
+ end
20
+
21
+ it "should default to depth_first traversal" do
22
+ expect(subject).to receive(:depth_first_traversal)
23
+ subject.traverse {}
24
+ end
25
+ end
26
+
27
+ describe 'depth first traversal' do
28
+
29
+ describe 'with unmodified tree' do
30
+ before do
31
+ setup_tree <<-ENDTREE
32
+ node1:
33
+ - node2:
34
+ - node3
35
+ - node4:
36
+ - node5
37
+ - node6
38
+ - node7
39
+ ENDTREE
40
+ end
41
+
42
+ it "should traverse correctly" do
43
+ result = []
44
+ node(:node1).traverse(:depth_first) { |node| result << node }
45
+ expect(result.collect { |n| n.name.to_sym }).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
46
+ end
47
+
48
+ it "should return and array containing the results of the block for each node" do
49
+ result = node(:node1).traverse(:depth_first) { |n| n.name.to_sym }
50
+ expect(result).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
51
+ end
52
+ end
53
+
54
+ describe 'with merged trees' do
55
+ before do
56
+ setup_tree <<-ENDTREE
57
+ - node4:
58
+ - node5
59
+ - node6:
60
+ - node7
61
+
62
+ - node1:
63
+ - node2:
64
+ - node3
65
+ ENDTREE
66
+
67
+ node(:node1).children << node(:node4)
68
+ end
69
+
70
+ it "should traverse correctly" do
71
+ result = node(:node1).traverse(:depth_first) { |n| n.name.to_sym }
72
+ expect(result).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
73
+ end
74
+ end
75
+
76
+ describe 'with reordered nodes' do
77
+
78
+ before do
79
+ setup_tree <<-ENDTREE
80
+ node1:
81
+ - node2:
82
+ - node3
83
+ - node4:
84
+ - node6
85
+ - node5
86
+ - node7
87
+ ENDTREE
88
+
89
+ node(:node5).move_above(node(:node6))
90
+ end
91
+
92
+ it 'should iterate through the nodes in the correct order' do
93
+ result = []
94
+ node(:node1).traverse(:depth_first) { |node| result << node }
95
+ expect(result.collect { |n| n.name.to_sym }).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
96
+ end
97
+
98
+ it 'should return the nodes in the correct order' do
99
+ result = node(:node1).traverse(:depth_first)
100
+ expect(result.collect { |n| n.name.to_sym }).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+
107
+ describe 'breadth first traversal' do
108
+
109
+ before do
110
+ setup_tree <<-ENDTREE
111
+ node1:
112
+ - node2:
113
+ - node5
114
+ - node3:
115
+ - node6
116
+ - node7
117
+ - node4
118
+ ENDTREE
119
+ end
120
+
121
+ it "should traverse correctly" do
122
+ result = []
123
+ node(:node1).traverse(:breadth_first) { |n| result << n }
124
+ expect(result.collect { |n| n.name.to_sym }).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
125
+ end
126
+
127
+ it "should return and array containing the results of the block for each node" do
128
+ result = node(:node1).traverse(:breadth_first) { |n| n.name.to_sym }
129
+ expect(result).to eq([:node1, :node2, :node3, :node4, :node5, :node6, :node7])
130
+ end
131
+
132
+ end
133
+
134
+ describe '.traverse' do
135
+ before :each do
136
+ setup_tree <<-ENDTREE
137
+ - root1
138
+ - root2
139
+ ENDTREE
140
+
141
+ @root1 = node(:root1)
142
+ @root2 = node(:root2)
143
+
144
+ Node.stub(:roots).and_return [@root1, @root2]
145
+ end
146
+
147
+ it 'should grab each root' do
148
+ expect(Node).to receive(:roots).and_return []
149
+
150
+ expect(Node.traverse).to eq([])
151
+ end
152
+
153
+ it 'should default the "type" arg to :depth_first' do
154
+ expect(@root1).to receive(:traverse).with(:depth_first).and_return([])
155
+ expect(@root2).to receive(:traverse).with(:depth_first).and_return([])
156
+
157
+ expect(Node.traverse).to eq([])
158
+ end
159
+
160
+ it 'should traverse each root' do
161
+ expect(@root1).to receive(:traverse).and_return([1, 2])
162
+ expect(@root2).to receive(:traverse).and_return([3, 4])
163
+
164
+ expect(Node.traverse).to eq([1, 2, 3, 4])
165
+ end
166
+
167
+ describe 'when the "type" arg is :breadth_first' do
168
+
169
+ it 'should traverse breadth-first' do
170
+ expect(@root1).to receive(:traverse).with(:breadth_first).and_return([])
171
+ expect(@root2).to receive(:traverse).with(:breadth_first).and_return([])
172
+
173
+ Node.traverse :breadth_first
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,444 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Tree do
4
+
5
+ subject { Node }
6
+
7
+ it "should reference many children as inverse of parent with index" do
8
+ a = Node.reflect_on_association(:children)
9
+ expect(a).to be
10
+ expect(a.macro).to eq(:has_many)
11
+ expect(a.class_name).to eq('Node')
12
+ expect(a.foreign_key).to eq('parent_id')
13
+ expect(Node.index_options).to have_key(:parent_id => 1)
14
+ end
15
+
16
+ it "should be referenced in one parent as inverse of children" do
17
+ a = Node.reflect_on_association(:parent)
18
+ expect(a).to be
19
+ expect(a.macro).to eq(:belongs_to)
20
+ expect(a.class_name).to eq('Node')
21
+ expect(a.inverse_of).to eq(:children)
22
+ end
23
+
24
+ it "should store parent_ids as Array with [] as default with index" do
25
+ f = Node.fields['parent_ids']
26
+ expect(f).to be
27
+ expect(f.options[:type]).to eq(Array)
28
+ expect(f.options[:default]).to eq([])
29
+ expect(Node.index_options).to have_key(:parent_ids => 1)
30
+ end
31
+
32
+ describe 'when new' do
33
+ it "should not require a saved parent when adding children" do
34
+ root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
35
+ expect { root.children << child; root.save! }.to_not raise_error
36
+ expect(child).to be_persisted
37
+ end
38
+
39
+ it "should not be saved when parent is not saved" do
40
+ root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
41
+ expect(child).not_to receive(:save)
42
+ root.children << child
43
+ end
44
+
45
+ it "should save its unsaved children" do
46
+ root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
47
+ root.children << child
48
+ expect(child).to receive(:save)
49
+ root.save
50
+ end
51
+ end
52
+
53
+ describe 'when saved' do
54
+
55
+ before(:each) do
56
+ setup_tree <<-ENDTREE
57
+ - root:
58
+ - child:
59
+ - subchild:
60
+ - subsubchild
61
+ - other_root:
62
+ - other_child
63
+ ENDTREE
64
+ end
65
+
66
+ it "should set the child's parent_id when added to parent's children" do
67
+ root = Node.create; child = Node.create
68
+ root.children << child
69
+ expect(child.parent).to eq(root)
70
+ expect(child.parent_id).to eq(root.id)
71
+ end
72
+
73
+ it "should set the child's parent_id parent is set on child" do
74
+ root = Node.create; child = Node.create
75
+ child.parent = root
76
+ expect(child.parent).to eq(root)
77
+ expect(child.parent_id).to eq(root.id)
78
+ end
79
+
80
+ it "should rebuild its parent_ids" do
81
+ root = Node.create; child = Node.create
82
+ root.children << child
83
+ expect(child.parent_ids).to eq([root.id])
84
+ end
85
+
86
+ it "should rebuild its children's parent_ids when its own parent_ids changed" do
87
+ other_root = node(:other_root); child = node(:child); subchild = node(:subchild);
88
+ other_root.children << child
89
+ subchild.reload # To get the updated version
90
+ expect(subchild.parent_ids).to eq([other_root.id, child.id])
91
+ end
92
+
93
+ it "should correctly rebuild its descendants' parent_ids when moved into an other subtree" do
94
+ subchild = node(:subchild); subsubchild = node(:subsubchild); other_child = node(:other_child)
95
+ other_child.children << subchild
96
+ subsubchild.reload
97
+ expect(subsubchild.parent_ids).to eq([node(:other_root).id, other_child.id, subchild.id])
98
+ end
99
+
100
+ it "should rebuild its children's parent_ids when its own parent_id is removed" do
101
+ c = node(:child)
102
+ c.parent_id = nil
103
+ c.save
104
+ expect(node(:subchild).parent_ids).to eq([node(:child).id])
105
+ end
106
+
107
+ it "should not rebuild its children's parent_ids when it's not required" do
108
+ root = node(:root)
109
+ expect(root).not_to receive(:rearrange_children)
110
+ root.save
111
+ end
112
+
113
+ it "should prevent cycles" do
114
+ child = node(:child)
115
+ child.parent = node(:subchild)
116
+ expect(child).not_to be_valid
117
+ expect(child.errors[:parent_id]).not_to be_nil
118
+ end
119
+
120
+ it "should save its children when added" do
121
+ new_child = Node.new(:name => 'new_child')
122
+ node(:root).children << new_child
123
+ expect(new_child).to be_persisted
124
+ end
125
+ end
126
+
127
+ describe 'when subclassed' do
128
+
129
+ before(:each) do
130
+ setup_tree <<-ENDTREE
131
+ - root:
132
+ - child:
133
+ - subchild
134
+ - other_child
135
+ - other_root
136
+ ENDTREE
137
+ end
138
+
139
+ it "should allow to store any subclass within the tree" do
140
+ subclassed = SubclassedNode.create!(:name => 'subclassed_subchild')
141
+ node(:child).children << subclassed
142
+ expect(subclassed.root).to eq(node(:root))
143
+ end
144
+
145
+ end
146
+
147
+ describe 'destroy strategies' do
148
+
149
+ before(:each) do
150
+ setup_tree <<-ENDTREE
151
+ - root:
152
+ - child:
153
+ - subchild
154
+ - other_child
155
+ - other_root
156
+ ENDTREE
157
+ end
158
+
159
+ describe ':nullify_children' do
160
+ it "should set its children's parent_id to null" do
161
+ node(:root).nullify_children
162
+ expect(node(:child)).to be_root
163
+ expect(node(:subchild).reload).not_to be_descendant_of node(:root)
164
+ end
165
+ end
166
+
167
+ describe ':move_children_to_parent' do
168
+ it "should set its childen's parent_id to the documents parent_id" do
169
+ node(:child).move_children_to_parent
170
+ expect(node(:child)).to be_leaf
171
+ expect(node(:root).children.to_a).to match_array([node(:child), node(:other_child), node(:subchild)])
172
+ end
173
+
174
+ it "should be able to handle a missing parent" do
175
+ node(:root).delete
176
+ expect { node(:child).move_children_to_parent }.to_not raise_error
177
+ end
178
+ end
179
+
180
+ describe ':destroy_children' do
181
+ it "should destroy all children" do
182
+ root = node(:root)
183
+ expect(root.children).to receive(:destroy_all)
184
+ root.destroy_children
185
+ end
186
+ end
187
+
188
+ describe ':delete_descendants' do
189
+ it "should delete all descendants" do
190
+ root = node(:root)
191
+ expect(Node).to receive(:delete_all).with(:conditions => { :parent_ids => root.id })
192
+ root.delete_descendants
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+ describe 'utility methods' do
199
+
200
+ before(:each) do
201
+ setup_tree <<-ENDTREE
202
+ - root:
203
+ - child:
204
+ - subchild
205
+ - other_child
206
+ - other_root
207
+ ENDTREE
208
+ end
209
+
210
+ describe '.root' do
211
+ it "should return the first root document" do
212
+ expect(Node.root).to eq(node(:root))
213
+ end
214
+ end
215
+
216
+ describe '.roots' do
217
+ it "should return all root documents" do
218
+ expect(Node.roots.to_a).to eq([node(:root), node(:other_root)])
219
+ end
220
+ end
221
+
222
+ describe '.leaves' do
223
+ it "should return all leaf documents" do
224
+ expect(Node.leaves.to_a).to match_array([node(:subchild), node(:other_child), node(:other_root)])
225
+ end
226
+ end
227
+
228
+ describe '#root?' do
229
+ it "should return true for root documents" do
230
+ expect(node(:root)).to be_root
231
+ end
232
+
233
+ it "should return false for non-root documents" do
234
+ expect(node(:child)).not_to be_root
235
+ end
236
+ end
237
+
238
+ describe '#leaf?' do
239
+ it "should return true for leaf documents" do
240
+ expect(node(:subchild)).to be_leaf
241
+ expect(node(:other_child)).to be_leaf
242
+ expect(Node.new).to be_leaf
243
+ end
244
+
245
+ it "should return false for non-leaf documents" do
246
+ expect(node(:child)).not_to be_leaf
247
+ expect(node(:root)).not_to be_leaf
248
+ end
249
+ end
250
+
251
+ describe '#depth' do
252
+ it "should return the depth of this document" do
253
+ expect(node(:root).depth).to eq(0)
254
+ expect(node(:child).depth).to eq(1)
255
+ expect(node(:subchild).depth).to eq(2)
256
+ end
257
+ end
258
+
259
+ describe '#root' do
260
+ it "should return the root for this document" do
261
+ expect(node(:subchild).root).to eq(node(:root))
262
+ end
263
+
264
+ it "should return itself when there is no root" do
265
+ new_node = Node.new
266
+ expect(new_node.root).to be(new_node)
267
+ end
268
+
269
+ it "should return it root when it's not saved yet" do
270
+ root = Node.new(:name => 'root')
271
+ new_node = Node.new(:name => 'child')
272
+ new_node.parent = root
273
+ expect(new_node.root).to be(root)
274
+ end
275
+ end
276
+
277
+ describe 'ancestors' do
278
+ describe '#ancestors' do
279
+ it "should return the documents ancestors" do
280
+ expect(node(:subchild).ancestors.to_a).to eq([node(:root), node(:child)])
281
+ end
282
+
283
+ it "should return the ancestors in correct order even after rearranging" do
284
+ setup_tree <<-ENDTREE
285
+ - root:
286
+ - child:
287
+ - subchild
288
+ ENDTREE
289
+
290
+ child = node(:child); child.parent = nil; child.save!
291
+ root = node(:root); root.parent = node(:child); root.save!
292
+ subchild = node(:subchild); subchild.parent = root; subchild.save!
293
+
294
+ expect(subchild.ancestors.to_a).to eq([child, root])
295
+ end
296
+
297
+ it 'should return nothing when there are no ancestors' do
298
+ root = Node.new(:name => 'root')
299
+ expect(root.ancestors).to be_empty
300
+ end
301
+
302
+ it 'should allow chaning of other `or`-criterias' do
303
+ setup_tree <<-ENDTREE
304
+ - root:
305
+ - child:
306
+ - subchild:
307
+ - subsubchild
308
+ ENDTREE
309
+
310
+ filtered_ancestors = node(:subsubchild).ancestors.or(
311
+ { :name => 'child' },
312
+ { :name => 'subchild' }
313
+ )
314
+
315
+ expect(filtered_ancestors.to_a).to eq([node(:child), node(:subchild)])
316
+ end
317
+ end
318
+
319
+ describe '#ancestors_and_self' do
320
+ it "should return the documents ancestors and itself" do
321
+ expect(node(:subchild).ancestors_and_self.to_a).to eq([node(:root), node(:child), node(:subchild)])
322
+ end
323
+ end
324
+
325
+ describe '#ancestor_of?' do
326
+ it "should return true for ancestors" do
327
+ expect(node(:child)).to be_ancestor_of(node(:subchild))
328
+ end
329
+
330
+ it "should return false for non-ancestors" do
331
+ expect(node(:other_child)).not_to be_ancestor_of(node(:subchild))
332
+ end
333
+ end
334
+ end
335
+
336
+ describe 'descendants' do
337
+ describe '#descendants' do
338
+ it "should return the documents descendants" do
339
+ expect(node(:root).descendants.to_a).to match_array([node(:child), node(:other_child), node(:subchild)])
340
+ end
341
+ end
342
+
343
+ describe '#descendants_and_self' do
344
+ it "should return the documents descendants and itself" do
345
+ expect(node(:root).descendants_and_self.to_a).to match_array([node(:root), node(:child), node(:other_child), node(:subchild)])
346
+ end
347
+ end
348
+
349
+ describe '#descendant_of?' do
350
+ it "should return true for descendants" do
351
+ subchild = node(:subchild)
352
+ expect(subchild).to be_descendant_of(node(:child))
353
+ expect(subchild).to be_descendant_of(node(:root))
354
+ end
355
+
356
+ it "should return false for non-descendants" do
357
+ expect(node(:subchild)).not_to be_descendant_of(node(:other_child))
358
+ end
359
+ end
360
+ end
361
+
362
+ describe 'siblings' do
363
+ describe '#siblings' do
364
+ it "should return the documents siblings" do
365
+ expect(node(:child).siblings.to_a).to eq([node(:other_child)])
366
+ end
367
+ end
368
+
369
+ describe '#siblings_and_self' do
370
+ it "should return the documents siblings and itself" do
371
+ expect(node(:child).siblings_and_self).to be_kind_of(Mongoid::Criteria)
372
+ expect(node(:child).siblings_and_self.to_a).to eq([node(:child), node(:other_child)])
373
+ end
374
+ end
375
+
376
+ describe '#sibling_of?' do
377
+ it "should return true for siblings" do
378
+ expect(node(:child)).to be_sibling_of(node(:other_child))
379
+ end
380
+
381
+ it "should return false for non-siblings" do
382
+ expect(node(:root)).not_to be_sibling_of(node(:other_child))
383
+ end
384
+ end
385
+ end
386
+
387
+ describe '#leaves' do
388
+ it "should return this documents leaves" do
389
+ expect(node(:root).leaves.to_a).to match_array([node(:other_child), node(:subchild)])
390
+ end
391
+ end
392
+
393
+ end
394
+
395
+ describe 'callbacks' do
396
+
397
+ after(:each) do
398
+ Node.reset_callbacks(:rearrange)
399
+ end
400
+
401
+ it "should provide a before_rearrange callback" do
402
+ expect(Node).to respond_to :before_rearrange
403
+ end
404
+
405
+ it "should provida an after_rearrange callback" do
406
+ expect(Node).to respond_to :after_rearrange
407
+ end
408
+
409
+ describe 'before rearrange callback' do
410
+
411
+ it "should be called before the document is rearranged" do
412
+ Node.before_rearrange :callback
413
+ node = Node.new
414
+ expect(node).to receive(:callback).ordered
415
+ expect(node).to receive(:rearrange).ordered
416
+ node.save
417
+ end
418
+
419
+ end
420
+
421
+ describe 'after rearrange callback' do
422
+
423
+ it "should be called after the document is rearranged" do
424
+ Node.after_rearrange :callback
425
+ node = Node.new
426
+ expect(node).to receive(:rearrange).ordered
427
+ expect(node).to receive(:callback).ordered
428
+ node.save
429
+ end
430
+
431
+ end
432
+
433
+ describe 'cascading to embedded documents' do
434
+
435
+ it 'should not raise a NoMethodError' do
436
+ node = NodeWithEmbeddedDocument.new
437
+ document = node.build_embedded_document
438
+ expect { node.save }.to_not raise_error
439
+ end
440
+
441
+ end
442
+
443
+ end
444
+ end