nobrainer-tree 0.0.1

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,460 @@
1
+ require 'spec_helper'
2
+
3
+ describe NoBrainer::Tree do
4
+
5
+ subject { Node }
6
+
7
+ it "should reference many children as inverse of parent with index" do
8
+ a = Node.association_metadata[:children]
9
+ expect(a).to be
10
+ expect(a.association_model).to eq(NoBrainer::Document::Association::HasMany)
11
+ expect(a.options[:class_name]).to eq('Node')
12
+ expect(a.options[:foreign_key]).to eq(:parent_id)
13
+ # TODO: Use index when nil is allowed in index queries
14
+ #expect(Node.fields[:parent_id][:index]).to be
15
+ end
16
+
17
+ it "should be referenced in one parent as inverse of children" do
18
+ a = Node.association_metadata[:parent]
19
+ expect(a).to be
20
+ expect(a.association_model).to eq(NoBrainer::Document::Association::BelongsTo)
21
+ expect(a.options[:class_name]).to eq('Node')
22
+ end
23
+
24
+ it "should store parent_ids as Array with [] as default with index" do
25
+ f = Node.fields[:parent_ids]
26
+ i = Node.indexes[:parent_ids]
27
+ expect(f).to be
28
+ expect(f[:type]).to eq(Array)
29
+ expect(f[:default]).to eq([])
30
+ expect(i).to be
31
+ expect(i[:multi]).to eq(true)
32
+ end
33
+
34
+ it "should store the depth as Integer with index" do
35
+ f = Node.fields[:depth]
36
+ i = Node.indexes[:depth]
37
+ expect(f).to be
38
+ expect(f[:type]).to eq(Integer)
39
+ expect(i).to be
40
+ end
41
+
42
+ # describe 'when new' do
43
+ # it "should not require a saved parent when adding children" do
44
+ # root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
45
+ # expect { root.children << child; root.save! }.to_not raise_error
46
+ # expect(child).to be_persisted
47
+ # end
48
+ #
49
+ # it "should not be saved when parent is not saved" do
50
+ # root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
51
+ # expect(child).not_to receive(:save)
52
+ # root.children << child
53
+ # end
54
+ #
55
+ # it "should save its unsaved children" do
56
+ # root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
57
+ # root.children << child
58
+ # expect(child).to receive(:save)
59
+ # root.save
60
+ # end
61
+ # end
62
+
63
+ describe 'when saved' do
64
+
65
+ before(:each) do
66
+ setup_tree <<-ENDTREE
67
+ - root:
68
+ - child:
69
+ - subchild:
70
+ - subsubchild
71
+ - other_root:
72
+ - other_child
73
+ ENDTREE
74
+ end
75
+
76
+ # it "should set the child's parent_id when added to parent's children" do
77
+ # root = Node.create; child = Node.create
78
+ # root.children << child
79
+ # expect(child.parent).to eq(root)
80
+ # expect(child.parent_id).to eq(root.id)
81
+ # end
82
+
83
+ it "should set the child's parent_id parent is set on child" do
84
+ root = Node.create; child = Node.create
85
+ child.parent = root
86
+ expect(child.parent).to eq(root)
87
+ expect(child.parent_id).to eq(root.id)
88
+ end
89
+
90
+ it "should rebuild its parent_ids" do
91
+ root = Node.create; child = Node.create
92
+ child.parent = root; child.save
93
+ expect(child.parent_ids).to eq([root.id])
94
+ end
95
+
96
+ it "should rebuild its children's parent_ids when its own parent_ids changed" do
97
+ other_root = node!(:other_root); child = node!(:child); subchild = node!(:subchild);
98
+ child.parent = other_root; child.save
99
+ subchild.reload # To get the updated version
100
+ expect(subchild.parent_ids).to eq([other_root.id, child.id])
101
+ end
102
+
103
+ it "should correctly rebuild its descendants' parent_ids when moved into an other subtree" do
104
+ subchild = node!(:subchild); subsubchild = node!(:subsubchild); other_child = node!(:other_child)
105
+ subchild.parent = other_child; subchild.save
106
+ subsubchild.reload
107
+ expect(subsubchild.parent_ids).to eq([node(:other_root).id, other_child.id, subchild.id])
108
+ end
109
+
110
+ it "should rebuild its children's parent_ids when its own parent_id is removed" do
111
+ c = node!(:child)
112
+ c.parent_id = nil
113
+ c.save
114
+ expect(node!(:subchild).parent_ids).to eq([node(:child).id])
115
+ end
116
+
117
+ it "should not rebuild its children's parent_ids when it's not required" do
118
+ root = node(:root)
119
+ expect(root).not_to receive(:rearrange_children)
120
+ root.save
121
+ end
122
+
123
+ it "should prevent cycles" do
124
+ child = node(:child)
125
+ child.parent = node(:subchild)
126
+ expect(child).not_to be_valid
127
+ expect(child.errors[:parent_id]).not_to be_nil
128
+ end
129
+
130
+ it "should save its children when added" do
131
+ new_child = Node.new(:name => 'new_child')
132
+ new_child.parent = node!(:root); new_child.save
133
+ expect(new_child).to be_persisted
134
+ end
135
+ end
136
+
137
+ describe 'when subclassed' do
138
+
139
+ before(:each) do
140
+ setup_tree <<-ENDTREE
141
+ - root:
142
+ - child:
143
+ - subchild
144
+ - other_child
145
+ - other_root
146
+ ENDTREE
147
+ end
148
+
149
+ it "should allow to store any subclass within the tree" do
150
+ subclassed = SubclassedNode.create!(:name => 'subclassed_subchild')
151
+ subclassed.parent = node!(:child)
152
+ expect(subclassed.root).to eq(node(:root))
153
+ end
154
+
155
+ end
156
+
157
+ describe 'destroy strategies' do
158
+
159
+ before(:each) do
160
+ setup_tree <<-ENDTREE
161
+ - root:
162
+ - child:
163
+ - subchild
164
+ - other_child
165
+ - other_root
166
+ ENDTREE
167
+ end
168
+
169
+ describe ':nullify_children' do
170
+ it "should set its children's parent_id to null" do
171
+ node!(:root).nullify_children
172
+ expect(node!(:child)).to be_root
173
+ expect(node!(:subchild)).not_to be_descendant_of node(:root)
174
+ end
175
+ end
176
+
177
+ describe ':move_children_to_parent' do
178
+ it "should set its childen's parent_id to the documents parent_id" do
179
+ node!(:child).move_children_to_parent
180
+ expect(node!(:child)).to be_leaf
181
+ expect(node!(:root).children.to_a).to match_array([node(:child), node(:other_child), node(:subchild)])
182
+ end
183
+
184
+ it "should be able to handle a missing parent" do
185
+ node!(:root).delete
186
+ expect { node!(:child).move_children_to_parent }.to_not raise_error
187
+ end
188
+ end
189
+
190
+ describe ':destroy_children' do
191
+ it "should destroy all children" do
192
+ root = node!(:root)
193
+ expect(root.children).to receive(:destroy_all)
194
+ root.destroy_children
195
+ end
196
+ end
197
+
198
+ # describe ':delete_descendants' do
199
+ # it "should delete all descendants" do
200
+ # root = node!(:root)
201
+ # expect(Node).to receive(:delete_all).with(:conditions => { :parent_ids => root.id })
202
+ # root.delete_descendants
203
+ # end
204
+ # end
205
+
206
+ end
207
+
208
+ describe 'utility methods' do
209
+
210
+ before(:each) do
211
+ setup_tree <<-ENDTREE
212
+ - root:
213
+ - child:
214
+ - subchild
215
+ - other_child
216
+ - other_root
217
+ ENDTREE
218
+ end
219
+
220
+ describe '.root' do
221
+ it "should return the first root document" do
222
+ expect(Node.root).to eq(node(:root))
223
+ end
224
+ end
225
+
226
+ describe '.roots' do
227
+ it "should return all root documents" do
228
+ expect(Node.roots.to_a).to eq([node(:root), node(:other_root)])
229
+ end
230
+ end
231
+
232
+ describe '.leaves' do
233
+ it "should return all leaf documents" do
234
+ expect(Node.leaves.to_a).to match_array([node(:subchild), node(:other_child), node(:other_root)])
235
+ end
236
+ end
237
+
238
+ describe '#root?' do
239
+ it "should return true for root documents" do
240
+ expect(node(:root)).to be_root
241
+ end
242
+
243
+ it "should return false for non-root documents" do
244
+ expect(node(:child)).not_to be_root
245
+ end
246
+ end
247
+
248
+ describe '#leaf?' do
249
+ it "should return true for leaf documents" do
250
+ expect(node(:subchild)).to be_leaf
251
+ expect(node(:other_child)).to be_leaf
252
+ expect(Node.new).to be_leaf
253
+ end
254
+
255
+ it "should return false for non-leaf documents" do
256
+ expect(node!(:child)).not_to be_leaf
257
+ expect(node!(:root)).not_to be_leaf
258
+ end
259
+ end
260
+
261
+ describe '#depth' do
262
+ it "should return the depth of this document" do
263
+ expect(node!(:root).depth).to eq(0)
264
+ expect(node!(:child).depth).to eq(1)
265
+ expect(node!(:subchild).depth).to eq(2)
266
+ end
267
+
268
+ it "should be updated when the nodes ancestors change" do
269
+ node!(:child).update_attributes(:parent => nil)
270
+ expect(node!(:child).depth).to eq(0)
271
+ expect(node!(:subchild).depth).to eq(1)
272
+ end
273
+ end
274
+
275
+ describe '#root' do
276
+ it "should return the root for this document" do
277
+ expect(node(:subchild).root).to eq(node(:root))
278
+ end
279
+
280
+ it "should return itself when there is no root" do
281
+ new_node = Node.new
282
+ expect(new_node.root).to be(new_node)
283
+ end
284
+
285
+ it "should return it root when it's not saved yet" do
286
+ root = Node.new(:name => 'root')
287
+ new_node = Node.new(:name => 'child')
288
+ new_node.parent = root
289
+ expect(new_node.root).to be(root)
290
+ end
291
+ end
292
+
293
+ describe 'ancestors' do
294
+ describe '#ancestors' do
295
+ it "should return the documents ancestors" do
296
+ expect(node(:subchild).ancestors.to_a).to eq([node(:root), node(:child)])
297
+ end
298
+
299
+ it "should return the ancestors in correct order even after rearranging" do
300
+ setup_tree <<-ENDTREE
301
+ - root:
302
+ - child:
303
+ - subchild
304
+ ENDTREE
305
+
306
+ child = node(:child); child.parent = nil; child.save!
307
+ root = node(:root); root.parent = node(:child); root.save!
308
+ subchild = node(:subchild); subchild.parent = root; subchild.save!
309
+
310
+ expect(subchild.ancestors.to_a).to eq([child, root])
311
+ end
312
+
313
+ it 'should return nothing when there are no ancestors' do
314
+ root = Node.create(:name => 'root')
315
+ expect(root.ancestors).to be_empty
316
+ end
317
+
318
+ it 'should allow chaning of other `or`-criterias' do
319
+ setup_tree <<-ENDTREE
320
+ - root:
321
+ - child:
322
+ - subchild:
323
+ - subsubchild
324
+ ENDTREE
325
+
326
+ # filtered_ancestors = node(:subsubchild).ancestors.or(
327
+ # { :name => 'child' },
328
+ # { :name => 'subchild' }
329
+ # )
330
+ #
331
+ # expect(filtered_ancestors.to_a).to eq([node(:child), node(:subchild)])
332
+ end
333
+ end
334
+
335
+ describe '#ancestors_and_self' do
336
+ it "should return the documents ancestors and itself" do
337
+ expect(node(:subchild).ancestors_and_self.to_a).to eq([node(:root), node(:child), node(:subchild)])
338
+ end
339
+ end
340
+
341
+ describe '#ancestor_of?' do
342
+ it "should return true for ancestors" do
343
+ expect(node(:child)).to be_ancestor_of(node(:subchild))
344
+ end
345
+
346
+ it "should return false for non-ancestors" do
347
+ expect(node(:other_child)).not_to be_ancestor_of(node(:subchild))
348
+ end
349
+ end
350
+ end
351
+
352
+ describe 'descendants' do
353
+ describe '#descendants' do
354
+ it "should return the documents descendants" do
355
+ expect(node(:root).descendants.to_a).to match_array([node(:child), node(:other_child), node(:subchild)])
356
+ end
357
+ end
358
+
359
+ describe '#descendants_and_self' do
360
+ it "should return the documents descendants and itself" do
361
+ expect(node(:root).descendants_and_self.to_a).to match_array([node(:root), node(:child), node(:other_child), node(:subchild)])
362
+ end
363
+ end
364
+
365
+ describe '#descendant_of?' do
366
+ it "should return true for descendants" do
367
+ subchild = node(:subchild)
368
+ expect(subchild).to be_descendant_of(node(:child))
369
+ expect(subchild).to be_descendant_of(node(:root))
370
+ end
371
+
372
+ it "should return false for non-descendants" do
373
+ expect(node(:subchild)).not_to be_descendant_of(node(:other_child))
374
+ end
375
+ end
376
+ end
377
+
378
+ describe 'siblings' do
379
+ describe '#siblings' do
380
+ it "should return the documents siblings" do
381
+ expect(node(:child).siblings.to_a).to eq([node(:other_child)])
382
+ end
383
+ end
384
+
385
+ describe '#siblings_and_self' do
386
+ it "should return the documents siblings and itself" do
387
+ expect(node(:child).siblings_and_self).to be_kind_of(NoBrainer::Criteria)
388
+ expect(node(:child).siblings_and_self.to_a).to eq([node(:child), node(:other_child)])
389
+ end
390
+ end
391
+
392
+ describe '#sibling_of?' do
393
+ it "should return true for siblings" do
394
+ expect(node(:child)).to be_sibling_of(node(:other_child))
395
+ end
396
+
397
+ it "should return false for non-siblings" do
398
+ expect(node(:root)).not_to be_sibling_of(node(:other_child))
399
+ end
400
+ end
401
+ end
402
+
403
+ describe '#leaves' do
404
+ it "should return this documents leaves" do
405
+ expect(node(:root).leaves.to_a).to match_array([node(:other_child), node(:subchild)])
406
+ end
407
+ end
408
+
409
+ end
410
+
411
+ describe 'callbacks' do
412
+
413
+ after(:each) do
414
+ Node.reset_callbacks(:rearrange)
415
+ end
416
+
417
+ it "should provide a before_rearrange callback" do
418
+ expect(Node).to respond_to :before_rearrange
419
+ end
420
+
421
+ it "should provida an after_rearrange callback" do
422
+ expect(Node).to respond_to :after_rearrange
423
+ end
424
+
425
+ describe 'before rearrange callback' do
426
+
427
+ it "should be called before the document is rearranged" do
428
+ Node.before_rearrange :callback
429
+ node = Node.new
430
+ expect(node).to receive(:callback).ordered
431
+ expect(node).to receive(:rearrange).ordered
432
+ node.save
433
+ end
434
+
435
+ end
436
+
437
+ describe 'after rearrange callback' do
438
+
439
+ it "should be called after the document is rearranged" do
440
+ Node.after_rearrange :callback
441
+ node = Node.new
442
+ expect(node).to receive(:rearrange).ordered
443
+ expect(node).to receive(:callback).ordered
444
+ node.save
445
+ end
446
+
447
+ end
448
+
449
+ # describe 'cascading to embedded documents' do
450
+ #
451
+ # it 'should not raise a NoMethodError' do
452
+ # node = NodeWithEmbeddedDocument.new
453
+ # document = node.build_embedded_document
454
+ # expect { node.save }.to_not raise_error
455
+ # end
456
+ #
457
+ # end
458
+
459
+ end
460
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'nobrainer'
5
+ require 'nobrainer/tree'
6
+
7
+ require 'rspec'
8
+
9
+ NoBrainer.configure do |config|
10
+ config.app_name = :nobrainer_tree
11
+ config.environment = :test
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
15
+
16
+ RSpec.configure do |config|
17
+ config.expect_with :rspec do |c|
18
+ c.syntax = :expect
19
+ end
20
+ config.mock_with :rspec
21
+ config.after(:each) { NoBrainer.purge! }
22
+ end
@@ -0,0 +1,54 @@
1
+ require 'yaml'
2
+
3
+ module NoBrainer::Tree::TreeMacros
4
+
5
+ def setup_tree(tree)
6
+ create_tree(YAML.load(tree))
7
+ end
8
+
9
+ def node(name)
10
+ @nodes[name]
11
+ end
12
+
13
+ def node!(name)
14
+ node(name).reload
15
+ end
16
+
17
+ def print_tree(node, inspect = false, depth = 0)
18
+ print ' ' * depth
19
+ print '- ' unless depth == 0
20
+ print node.name
21
+ print " (#{node.inspect})" if inspect
22
+ print ':' if node.children.any?
23
+ print "\n"
24
+ node.children.each { |c| print_tree(c, inspect, depth + 1) }
25
+ end
26
+
27
+ private
28
+
29
+ def create_tree(parent=nil, object)
30
+ case object
31
+ when String then return create_node(parent, object)
32
+ when Array then object.each { |tree| create_tree(parent, tree) }
33
+ when Hash then
34
+ name, children = object.first
35
+ node = create_node(parent, name)
36
+ children.each { |child| create_tree(node, child) }
37
+ return node
38
+ end
39
+ end
40
+
41
+ def create_node(parent=nil, name)
42
+ @nodes ||= HashWithIndifferentAccess.new
43
+ @nodes[name] = subject.create(parent: parent, :name => name)
44
+ end
45
+
46
+ # def create_node(parent=nil, name)
47
+ # @nodes ||= HashWithIndifferentAccess.new
48
+ # @nodes[name] = Route.create(parent: parent, :name => name)
49
+ # end
50
+ end
51
+
52
+ RSpec.configure do |config|
53
+ config.include NoBrainer::Tree::TreeMacros
54
+ end
@@ -0,0 +1,34 @@
1
+ class Node
2
+ include NoBrainer::Document
3
+ include NoBrainer::Tree
4
+ include NoBrainer::Tree::Traversal
5
+
6
+ field :name
7
+ end
8
+
9
+ class SubclassedNode < Node
10
+ end
11
+
12
+ # Adding ordering on subclasses currently doesn't work as expected.
13
+ #
14
+ # class OrderedNode < Node
15
+ # include NoBrainer::Tree::Ordering
16
+ # end
17
+ class OrderedNode
18
+ include NoBrainer::Document
19
+ include NoBrainer::Tree
20
+ include NoBrainer::Tree::Traversal
21
+ include NoBrainer::Tree::Ordering
22
+
23
+ field :name
24
+ end
25
+
26
+ NoBrainer.sync_indexes
27
+
28
+ # class NodeWithEmbeddedDocument < Node
29
+ # embeds_one :embedded_document, :cascade_callbacks => true
30
+ # end
31
+ #
32
+ # class EmbeddedDocument
33
+ # include Mongoid::Document
34
+ # end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nobrainer-tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steven Eksteen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nobrainer
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ description: A tree structure for NoBrainer documents using the materialized path
70
+ pattern
71
+ email:
72
+ - steven@secondimpression.net
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rspec"
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - lib/nobrainer/tree.rb
83
+ - lib/nobrainer/tree/ordering.rb
84
+ - lib/nobrainer/tree/traversal.rb
85
+ - spec/nobrainer/tree/ordering_spec.rb
86
+ - spec/nobrainer/tree/traversal_spec.rb
87
+ - spec/nobrainer/tree_spec.rb
88
+ - spec/spec_helper.rb
89
+ - spec/support/macros/tree_macros.rb
90
+ - spec/support/models/node.rb
91
+ homepage: https://github.com/secondimpression/nobrainer-tree
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.4.3
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: A tree structure for NoBrainer documents
115
+ test_files: []
116
+ has_rdoc: