mongoid-tree 0.7.0 → 1.0.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.
- data/Gemfile +2 -2
- data/LICENSE +1 -1
- data/README.md +215 -0
- data/Rakefile +2 -8
- data/lib/mongoid/tree.rb +164 -45
- data/lib/mongoid/tree/ordering.rb +30 -0
- data/lib/mongoid/tree/traversal.rb +34 -36
- data/spec/mongoid/tree/traversal_spec.rb +50 -36
- data/spec/mongoid/tree_spec.rb +23 -13
- data/spec/spec_helper.rb +2 -5
- data/spec/support/models/node.rb +8 -0
- metadata +43 -33
- data/README.rdoc +0 -192
@@ -44,6 +44,8 @@ module Mongoid
|
|
44
44
|
|
45
45
|
##
|
46
46
|
# Returns a chainable criteria for this document's ancestors
|
47
|
+
#
|
48
|
+
# @return [Mongoid::Criteria] Mongoid criteria to retrieve the documents ancestors
|
47
49
|
def ancestors
|
48
50
|
base_class.unscoped.where(:_id.in => parent_ids)
|
49
51
|
end
|
@@ -51,6 +53,8 @@ module Mongoid
|
|
51
53
|
##
|
52
54
|
# Returns siblings below the current document.
|
53
55
|
# Siblings with a position greater than this documents's position.
|
56
|
+
#
|
57
|
+
# @return [Mongoid::Criteria] Mongoid criteria to retrieve the documents lower_siblings
|
54
58
|
def lower_siblings
|
55
59
|
self.siblings.where(:position.gt => self.position)
|
56
60
|
end
|
@@ -58,36 +62,48 @@ module Mongoid
|
|
58
62
|
##
|
59
63
|
# Returns siblings above the current document.
|
60
64
|
# Siblings with a position lower than this documents's position.
|
65
|
+
#
|
66
|
+
# @return [Mongoid::Criteria] Mongoid criteria to retrieve the documents higher_siblings
|
61
67
|
def higher_siblings
|
62
68
|
self.siblings.where(:position.lt => self.position)
|
63
69
|
end
|
64
70
|
|
65
71
|
##
|
66
72
|
# Returns the lowest sibling (could be self)
|
73
|
+
#
|
74
|
+
# @return [Mongoid::Document] The lowest sibling
|
67
75
|
def last_sibling_in_list
|
68
76
|
siblings_and_self.last
|
69
77
|
end
|
70
78
|
|
71
79
|
##
|
72
80
|
# Returns the highest sibling (could be self)
|
81
|
+
#
|
82
|
+
# @return [Mongoid::Document] The highest sibling
|
73
83
|
def first_sibling_in_list
|
74
84
|
siblings_and_self.first
|
75
85
|
end
|
76
86
|
|
77
87
|
##
|
78
88
|
# Is this the highest sibling?
|
89
|
+
#
|
90
|
+
# @return [Boolean] Whether the document is the highest sibling
|
79
91
|
def at_top?
|
80
92
|
higher_siblings.empty?
|
81
93
|
end
|
82
94
|
|
83
95
|
##
|
84
96
|
# Is this the lowest sibling?
|
97
|
+
#
|
98
|
+
# @return [Boolean] Whether the document is the lowest sibling
|
85
99
|
def at_bottom?
|
86
100
|
lower_siblings.empty?
|
87
101
|
end
|
88
102
|
|
89
103
|
##
|
90
104
|
# Move this node above all its siblings
|
105
|
+
#
|
106
|
+
# @return [undefined]
|
91
107
|
def move_to_top
|
92
108
|
return true if at_top?
|
93
109
|
move_above(first_sibling_in_list)
|
@@ -95,6 +111,8 @@ module Mongoid
|
|
95
111
|
|
96
112
|
##
|
97
113
|
# Move this node below all its siblings
|
114
|
+
#
|
115
|
+
# @return [undefined]
|
98
116
|
def move_to_bottom
|
99
117
|
return true if at_bottom?
|
100
118
|
move_below(last_sibling_in_list)
|
@@ -102,6 +120,8 @@ module Mongoid
|
|
102
120
|
|
103
121
|
##
|
104
122
|
# Move this node one position up
|
123
|
+
#
|
124
|
+
# @return [undefined]
|
105
125
|
def move_up
|
106
126
|
return if at_top?
|
107
127
|
siblings.where(:position => self.position - 1).first.inc(:position, 1)
|
@@ -110,6 +130,8 @@ module Mongoid
|
|
110
130
|
|
111
131
|
##
|
112
132
|
# Move this node one position down
|
133
|
+
#
|
134
|
+
# @return [undefined]
|
113
135
|
def move_down
|
114
136
|
return if at_bottom?
|
115
137
|
siblings.where(:position => self.position + 1).first.inc(:position, -1)
|
@@ -120,6 +142,10 @@ module Mongoid
|
|
120
142
|
# Move this node above the specified node
|
121
143
|
#
|
122
144
|
# This method changes the node's parent if nescessary.
|
145
|
+
#
|
146
|
+
# @param [Mongoid::Tree] other document to move this document above
|
147
|
+
#
|
148
|
+
# @return [undefined]
|
123
149
|
def move_above(other)
|
124
150
|
unless sibling_of?(other)
|
125
151
|
self.parent_id = other.parent_id
|
@@ -144,6 +170,10 @@ module Mongoid
|
|
144
170
|
# Move this node below the specified node
|
145
171
|
#
|
146
172
|
# This method changes the node's parent if nescessary.
|
173
|
+
#
|
174
|
+
# @param [Mongoid::Tree] other document to move this document below
|
175
|
+
#
|
176
|
+
# @return [undefined]
|
147
177
|
def move_below(other)
|
148
178
|
unless sibling_of?(other)
|
149
179
|
self.parent_id = other.parent_id
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Mongoid
|
1
|
+
module Mongoid
|
2
2
|
module Tree
|
3
3
|
##
|
4
4
|
# = Mongoid::Tree::Traversal
|
@@ -48,37 +48,34 @@ module Mongoid # :nodoc:
|
|
48
48
|
module Traversal
|
49
49
|
extend ActiveSupport::Concern
|
50
50
|
|
51
|
-
##
|
52
|
-
# :singleton-method: traverse
|
53
|
-
# Traverses the entire tree, one root at a time, using the given traversal
|
54
|
-
# method (Default is :depth_first).
|
55
|
-
#
|
56
|
-
# See Mongoid::Tree::Traversal for available traversal methods.
|
57
|
-
#
|
58
|
-
# Example:
|
59
|
-
#
|
60
|
-
# # Say we have the following tree, and want to print its hierarchy:
|
61
|
-
# # root_1
|
62
|
-
# # child_1_a
|
63
|
-
# # root_2
|
64
|
-
# # child_2_a
|
65
|
-
# # child_2_a_1
|
66
|
-
#
|
67
|
-
# Node.traverse(:depth_first) do |node|
|
68
|
-
# indentation = ' ' * node.depth
|
69
|
-
#
|
70
|
-
# puts "#{indentation}#{node.name}"
|
71
|
-
# end
|
72
|
-
#
|
73
51
|
|
74
52
|
##
|
75
|
-
#
|
76
|
-
#
|
77
|
-
module ClassMethods
|
53
|
+
# This module implements class methods that will be available
|
54
|
+
# on the document that includes Mongoid::Tree::Traversal
|
55
|
+
module ClassMethods
|
56
|
+
##
|
57
|
+
# Traverses the entire tree, one root at a time, using the given traversal
|
58
|
+
# method (Default is :depth_first).
|
59
|
+
#
|
60
|
+
# See Mongoid::Tree::Traversal for available traversal methods.
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
#
|
64
|
+
# # Say we have the following tree, and want to print its hierarchy:
|
65
|
+
# # root_1
|
66
|
+
# # child_1_a
|
67
|
+
# # root_2
|
68
|
+
# # child_2_a
|
69
|
+
# # child_2_a_1
|
70
|
+
#
|
71
|
+
# Node.traverse(:depth_first) do |node|
|
72
|
+
# indentation = ' ' * node.depth
|
73
|
+
#
|
74
|
+
# puts "#{indentation}#{node.name}"
|
75
|
+
# end
|
76
|
+
#
|
78
77
|
def traverse(type = :depth_first, &block)
|
79
|
-
|
80
|
-
roots.each { |root| res.concat root.traverse(type, &block) }
|
81
|
-
res
|
78
|
+
roots.collect { |root| root.traverse(type, &block) }.flatten
|
82
79
|
end
|
83
80
|
end
|
84
81
|
|
@@ -88,7 +85,7 @@ module Mongoid # :nodoc:
|
|
88
85
|
#
|
89
86
|
# See Mongoid::Tree::Traversal for available traversal methods.
|
90
87
|
#
|
91
|
-
#
|
88
|
+
# @example
|
92
89
|
#
|
93
90
|
# results = []
|
94
91
|
# root.traverse(:depth_first) do |node|
|
@@ -96,28 +93,29 @@ module Mongoid # :nodoc:
|
|
96
93
|
# end
|
97
94
|
#
|
98
95
|
# root.traverse(:depth_first).map(&:name)
|
96
|
+
# root.traverse(:depth_first, &:name)
|
99
97
|
#
|
100
98
|
def traverse(type = :depth_first, &block)
|
101
|
-
|
102
|
-
block ||= lambda { |node| res << node }
|
99
|
+
block ||= lambda { |node| node }
|
103
100
|
send("#{type}_traversal", &block)
|
104
|
-
res
|
105
101
|
end
|
106
102
|
|
107
103
|
private
|
108
104
|
|
109
105
|
def depth_first_traversal(&block)
|
110
|
-
block.call(self)
|
111
|
-
|
106
|
+
result = [block.call(self)] + self.children.collect { |c| c.send(:depth_first_traversal, &block) }
|
107
|
+
result.flatten
|
112
108
|
end
|
113
109
|
|
114
110
|
def breadth_first_traversal(&block)
|
111
|
+
result = []
|
115
112
|
queue = [self]
|
116
113
|
while queue.any? do
|
117
114
|
node = queue.shift
|
118
|
-
block.call(node)
|
115
|
+
result << block.call(node)
|
119
116
|
queue += node.children
|
120
117
|
end
|
118
|
+
result
|
121
119
|
end
|
122
120
|
end
|
123
121
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Mongoid::Tree::Traversal do
|
4
4
|
|
5
|
-
subject {
|
5
|
+
subject { OrderedNode }
|
6
6
|
|
7
7
|
describe '#traverse' do
|
8
8
|
|
@@ -22,53 +22,59 @@ describe Mongoid::Tree::Traversal do
|
|
22
22
|
subject.should_receive(:depth_first_traversal)
|
23
23
|
subject.traverse {}
|
24
24
|
end
|
25
|
-
|
26
25
|
end
|
27
26
|
|
28
27
|
describe 'depth first traversal' do
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
-
|
35
|
-
|
36
|
-
-
|
37
|
-
|
38
|
-
|
39
|
-
ENDTREE
|
40
|
-
|
41
|
-
result = []
|
42
|
-
node(:node1).traverse(:depth_first) { |node| result << node }
|
43
|
-
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should traverse correctly on merged trees" do
|
47
|
-
|
48
|
-
setup_tree <<-ENDTREE
|
49
|
-
- node4:
|
50
|
-
- node5
|
51
|
-
- node6:
|
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
|
52
38
|
- node7
|
39
|
+
ENDTREE
|
40
|
+
end
|
53
41
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
42
|
+
it "should traverse correctly" do
|
43
|
+
result = []
|
44
|
+
node(:node1).traverse(:depth_first) { |node| result << node }
|
45
|
+
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
46
|
+
end
|
58
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
|
+
result.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
51
|
+
end
|
52
|
+
end
|
59
53
|
|
60
|
-
|
54
|
+
describe 'with merged trees' do
|
55
|
+
before do
|
56
|
+
setup_tree <<-ENDTREE
|
57
|
+
- node4:
|
58
|
+
- node5
|
59
|
+
- node6:
|
60
|
+
- node7
|
61
61
|
|
62
|
+
- node1:
|
63
|
+
- node2:
|
64
|
+
- node3
|
65
|
+
ENDTREE
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
+
result.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
73
|
+
end
|
66
74
|
end
|
67
75
|
|
68
76
|
describe 'with reordered nodes' do
|
69
77
|
|
70
|
-
subject { OrderedNode }
|
71
|
-
|
72
78
|
before do
|
73
79
|
setup_tree <<-ENDTREE
|
74
80
|
node1:
|
@@ -79,6 +85,7 @@ describe Mongoid::Tree::Traversal do
|
|
79
85
|
- node5
|
80
86
|
- node7
|
81
87
|
ENDTREE
|
88
|
+
|
82
89
|
node(:node5).move_above(node(:node6))
|
83
90
|
end
|
84
91
|
|
@@ -99,8 +106,8 @@ describe Mongoid::Tree::Traversal do
|
|
99
106
|
|
100
107
|
describe 'breadth first traversal' do
|
101
108
|
|
102
|
-
|
103
|
-
|
109
|
+
before do
|
110
|
+
setup_tree <<-ENDTREE
|
104
111
|
node1:
|
105
112
|
- node2:
|
106
113
|
- node5
|
@@ -109,12 +116,19 @@ describe Mongoid::Tree::Traversal do
|
|
109
116
|
- node7
|
110
117
|
- node4
|
111
118
|
ENDTREE
|
119
|
+
end
|
112
120
|
|
121
|
+
it "should traverse correctly" do
|
113
122
|
result = []
|
114
123
|
node(:node1).traverse(:breadth_first) { |n| result << n }
|
115
124
|
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
116
125
|
end
|
117
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
|
+
result.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
130
|
+
end
|
131
|
+
|
118
132
|
end
|
119
133
|
|
120
134
|
describe '.traverse' do
|
data/spec/mongoid/tree_spec.rb
CHANGED
@@ -7,16 +7,16 @@ describe Mongoid::Tree do
|
|
7
7
|
it "should reference many children as inverse of parent with index" do
|
8
8
|
a = Node.reflect_on_association(:children)
|
9
9
|
a.should be
|
10
|
-
a.macro.should eql(:
|
10
|
+
a.macro.should eql(:has_many)
|
11
11
|
a.class_name.should eql('Node')
|
12
12
|
a.foreign_key.should eql('parent_id')
|
13
|
-
Node.index_options.should have_key('parent_id')
|
13
|
+
Node.index_options.should have_key('parent_id' => 1)
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should be referenced in one parent as inverse of children" do
|
17
17
|
a = Node.reflect_on_association(:parent)
|
18
18
|
a.should be
|
19
|
-
a.macro.should eql(:
|
19
|
+
a.macro.should eql(:belongs_to)
|
20
20
|
a.class_name.should eql('Node')
|
21
21
|
a.inverse_of.should eql(:children)
|
22
22
|
end
|
@@ -26,7 +26,7 @@ describe Mongoid::Tree do
|
|
26
26
|
f.should be
|
27
27
|
f.options[:type].should eql(Array)
|
28
28
|
f.options[:default].should eql([])
|
29
|
-
Node.index_options.should have_key(:parent_ids)
|
29
|
+
Node.index_options.should have_key(:parent_ids => 1)
|
30
30
|
end
|
31
31
|
|
32
32
|
describe 'when new' do
|
@@ -45,7 +45,7 @@ describe Mongoid::Tree do
|
|
45
45
|
it "should save its unsaved children" do
|
46
46
|
root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
|
47
47
|
root.children << child
|
48
|
-
child.should_receive(:save)
|
48
|
+
child.should_receive(:save)
|
49
49
|
root.save
|
50
50
|
end
|
51
51
|
end
|
@@ -66,15 +66,15 @@ describe Mongoid::Tree do
|
|
66
66
|
it "should set the child's parent_id when added to parent's children" do
|
67
67
|
root = Node.create; child = Node.create
|
68
68
|
root.children << child
|
69
|
-
child.parent.should
|
70
|
-
child.parent_id.should
|
69
|
+
child.parent.should eq(root)
|
70
|
+
child.parent_id.should eq(root.id)
|
71
71
|
end
|
72
72
|
|
73
73
|
it "should set the child's parent_id parent is set on child" do
|
74
74
|
root = Node.create; child = Node.create
|
75
75
|
child.parent = root
|
76
|
-
child.parent.should
|
77
|
-
child.parent_id.should
|
76
|
+
child.parent.should eq(root)
|
77
|
+
child.parent_id.should eq(root.id)
|
78
78
|
end
|
79
79
|
|
80
80
|
it "should rebuild its parent_ids" do
|
@@ -245,9 +245,9 @@ describe Mongoid::Tree do
|
|
245
245
|
|
246
246
|
describe '#depth' do
|
247
247
|
it "should return the depth of this document" do
|
248
|
-
node(:root).depth.should
|
249
|
-
node(:child).depth.should
|
250
|
-
node(:subchild).depth.should
|
248
|
+
node(:root).depth.should eq(0)
|
249
|
+
node(:child).depth.should eq(1)
|
250
|
+
node(:subchild).depth.should eq(2)
|
251
251
|
end
|
252
252
|
end
|
253
253
|
|
@@ -317,7 +317,7 @@ describe Mongoid::Tree do
|
|
317
317
|
end
|
318
318
|
|
319
319
|
it "#siblings_and_self should return the documents siblings and itself" do
|
320
|
-
node(:child).siblings_and_self.
|
320
|
+
node(:child).siblings_and_self.should be_kind_of(Mongoid::Criteria)
|
321
321
|
node(:child).siblings_and_self.to_a.should == [node(:child), node(:other_child)]
|
322
322
|
end
|
323
323
|
|
@@ -378,5 +378,15 @@ describe Mongoid::Tree do
|
|
378
378
|
|
379
379
|
end
|
380
380
|
|
381
|
+
describe 'cascading to embedded documents' do
|
382
|
+
|
383
|
+
it 'should not raise a NoMethodError' do
|
384
|
+
node = NodeWithEmbeddedDocument.new
|
385
|
+
document = node.build_embedded_document
|
386
|
+
expect { node.save }.to_not raise_error NoMethodError
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
|
381
391
|
end
|
382
392
|
end
|