mongoid-tree 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/README.rdoc +44 -3
- data/Rakefile +2 -2
- data/lib/mongoid/tree.rb +128 -39
- data/lib/mongoid/tree/traversal.rb +10 -10
- data/spec/mongoid/tree/traversal_spec.rb +24 -24
- data/spec/mongoid/tree_spec.rb +118 -46
- data/spec/spec_helper.rb +1 -1
- data/spec/support/macros/tree_macros.rb +11 -11
- data/spec/support/models/node.rb +1 -1
- metadata +4 -4
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -10,12 +10,12 @@ A tree structure for Mongoid documents using the materialized path pattern
|
|
10
10
|
|
11
11
|
To install mongoid_tree, simply add it to your Gemfile:
|
12
12
|
|
13
|
-
gem
|
13
|
+
gem 'mongoid-tree'
|
14
14
|
|
15
15
|
In order to get the latest development version of mongoid-tree:
|
16
16
|
|
17
|
-
gem
|
18
|
-
|
17
|
+
gem 'mongoid-tree' :git => 'git://github.com/benedikt/mongoid-tree'
|
18
|
+
|
19
19
|
You might want to add the <tt>:require => 'mongoid/tree'</tt> option as well and finally run
|
20
20
|
|
21
21
|
bundle install
|
@@ -31,6 +31,47 @@ Read the API documentation at http://benedikt.github.com/mongoid-tree and take a
|
|
31
31
|
include Mongoid::Tree
|
32
32
|
end
|
33
33
|
|
34
|
+
=== Utility methods
|
35
|
+
|
36
|
+
There are several utility methods that help getting to other related documents in the tree:
|
37
|
+
|
38
|
+
Node.root
|
39
|
+
Node.roots
|
40
|
+
Node.leaves
|
41
|
+
|
42
|
+
node.root
|
43
|
+
node.parent
|
44
|
+
node.children
|
45
|
+
node.ancestors
|
46
|
+
node.ancestors_and_self
|
47
|
+
node.descendants
|
48
|
+
node.descendants_and_self
|
49
|
+
node.siblings
|
50
|
+
node.siblings_and_self
|
51
|
+
node.leaves
|
52
|
+
|
53
|
+
In addition it's possible to check certain aspects of the documents position in the tree:
|
54
|
+
|
55
|
+
node.root?
|
56
|
+
node.leaf?
|
57
|
+
node.depth
|
58
|
+
node.ancestor_of?(other)
|
59
|
+
node.descendant_of?(other)
|
60
|
+
|
61
|
+
See Mongoid::Tree for more information on these methods.
|
62
|
+
|
63
|
+
=== Traversal
|
64
|
+
|
65
|
+
It's possible to traverse the tree using different traversal methods. See Mongoid::Tree::Traversal for details
|
66
|
+
|
67
|
+
node.traverse(:breadth_first) do |n|
|
68
|
+
# Do something with Node n
|
69
|
+
end
|
70
|
+
|
71
|
+
=== Callbacks
|
72
|
+
|
73
|
+
There are two callbacks that are called before and after the rearranging process. This enables you to do additional computations after the documents position in the tree is updated. See Mongoid::Tree for details.
|
74
|
+
|
34
75
|
== Known issues
|
35
76
|
|
36
77
|
See http://github.com/benedikt/mongoid-tree/issues
|
data/Rakefile
CHANGED
data/lib/mongoid/tree.rb
CHANGED
@@ -4,18 +4,18 @@ module Mongoid # :nodoc:
|
|
4
4
|
##
|
5
5
|
# = Mongoid::Tree
|
6
6
|
#
|
7
|
-
# This module extends any Mongoid document with tree functionality.
|
8
|
-
#
|
7
|
+
# This module extends any Mongoid document with tree functionality.
|
8
|
+
#
|
9
9
|
# == Usage
|
10
10
|
#
|
11
11
|
# Simply include the module in any Mongoid document:
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# class Node
|
14
14
|
# include Mongoid::Document
|
15
15
|
# include Mongoid::Tree
|
16
16
|
# end
|
17
17
|
#
|
18
|
-
# === Using the tree structure
|
18
|
+
# === Using the tree structure
|
19
19
|
#
|
20
20
|
# Each document references many children. You can access them using the <tt>#children</tt> method.
|
21
21
|
#
|
@@ -29,129 +29,218 @@ module Mongoid # :nodoc:
|
|
29
29
|
# node.parent # => nil
|
30
30
|
# node.children.create
|
31
31
|
# node.children.first.parent # => node
|
32
|
-
#
|
32
|
+
#
|
33
|
+
# === Callbacks
|
34
|
+
#
|
35
|
+
# Mongoid::Tree offers callbacks for its rearranging process. This enables you to
|
36
|
+
# rebuild certain fields when the document was moved in the tree. Rearranging happens
|
37
|
+
# before the document is validated. This gives you a chance to validate your additional
|
38
|
+
# changes done in your callbacks. See ActiveModel::Callbacks and ActiveSupport::Callbacks
|
39
|
+
# for further details on callbacks.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# class Page
|
44
|
+
# include Mongoid::Document
|
45
|
+
# include Mongoid::Tree
|
46
|
+
#
|
47
|
+
# after_rearrange :rebuild_path
|
48
|
+
#
|
49
|
+
# field :slug
|
50
|
+
# field :path
|
51
|
+
#
|
52
|
+
# private
|
53
|
+
#
|
54
|
+
# def rebuild_path
|
55
|
+
# self.path = self.ancestors_and_self.collect(&:slug).join('/')
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
33
59
|
module Tree
|
34
60
|
extend ActiveSupport::Concern
|
35
61
|
|
36
62
|
include Traversal
|
37
|
-
|
63
|
+
|
38
64
|
included do
|
39
|
-
|
65
|
+
references_many :children, :class_name => self.name, :foreign_key => :parent_id, :inverse_of => :parent
|
40
66
|
referenced_in :parent, :class_name => self.name, :inverse_of => :children
|
41
|
-
|
67
|
+
|
42
68
|
field :parent_ids, :type => Array, :default => []
|
43
|
-
|
44
|
-
set_callback :validation, :before, :rearrange
|
69
|
+
|
45
70
|
set_callback :save, :after, :rearrange_children, :if => :rearrange_children?
|
71
|
+
set_callback :validation, :before do
|
72
|
+
run_callbacks(:rearrange) { rearrange }
|
73
|
+
end
|
74
|
+
|
75
|
+
define_model_callbacks :rearrange, :only => [:before, :after]
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# :singleton-method: root
|
80
|
+
# Returns the first root document
|
81
|
+
|
82
|
+
##
|
83
|
+
# :singleton-method: roots
|
84
|
+
# Returns all root documents
|
85
|
+
|
86
|
+
##
|
87
|
+
# :singleton-method: leaves
|
88
|
+
# Returns all leaves (be careful, currently involves two queries)
|
89
|
+
|
90
|
+
##
|
91
|
+
# This module includes those methods documented above
|
92
|
+
module ClassMethods # :nodoc:
|
93
|
+
|
94
|
+
def root
|
95
|
+
first(:conditions => { :parent_id => nil })
|
96
|
+
end
|
97
|
+
|
98
|
+
def roots
|
99
|
+
where(:parent_id => nil)
|
100
|
+
end
|
101
|
+
|
102
|
+
def leaves
|
103
|
+
where(:_id.nin => only(:parent_id).collect(&:parent_id))
|
104
|
+
end
|
105
|
+
|
46
106
|
end
|
47
|
-
|
107
|
+
|
108
|
+
##
|
109
|
+
# :singleton-method: before_rearrange
|
110
|
+
# Sets a callback that is called before the document is rearranged
|
111
|
+
# (Generated by ActiveSupport)
|
112
|
+
|
113
|
+
##
|
114
|
+
# :singleton-method: after_rearrange
|
115
|
+
# Sets a callback that is called after the document is rearranged
|
116
|
+
# (Generated by ActiveSupport)
|
117
|
+
|
48
118
|
##
|
49
119
|
# :method: children
|
50
|
-
# Returns a list of the document's children. It's a <tt>
|
120
|
+
# Returns a list of the document's children. It's a <tt>references_many</tt> association.
|
51
121
|
# (Generated by Mongoid)
|
52
|
-
|
122
|
+
|
53
123
|
##
|
54
124
|
# :method: parent
|
55
125
|
# Returns the document's parent (unless it's a root document). It's a <tt>referenced_in</tt> association.
|
56
126
|
# (Generated by Mongoid)
|
57
|
-
|
127
|
+
|
128
|
+
##
|
129
|
+
# :method: parent=
|
130
|
+
#call-seq:
|
131
|
+
# parent= document
|
132
|
+
#
|
133
|
+
# Sets this documents parent document.
|
134
|
+
# (Generated by Mongoid)
|
135
|
+
|
58
136
|
##
|
59
137
|
# :method: parent_ids
|
60
138
|
# Returns a list of the document's parent_ids, starting with the root node.
|
61
139
|
# (Generated by Mongoid)
|
62
|
-
|
140
|
+
|
63
141
|
##
|
64
|
-
# Is this document a root node (has no parent)?
|
142
|
+
# Is this document a root node (has no parent)?
|
65
143
|
def root?
|
66
144
|
parent_id.nil?
|
67
145
|
end
|
68
|
-
|
146
|
+
|
69
147
|
##
|
70
148
|
# Is this document a leaf node (has no children)?
|
71
149
|
def leaf?
|
72
150
|
children.empty?
|
73
151
|
end
|
74
|
-
|
152
|
+
|
153
|
+
##
|
154
|
+
# Returns the depth of this document (number of ancestors)
|
155
|
+
def depth
|
156
|
+
parent_ids.count
|
157
|
+
end
|
158
|
+
|
75
159
|
##
|
76
160
|
# Returns this document's root node
|
77
161
|
def root
|
78
162
|
self.class.find(parent_ids.first)
|
79
163
|
end
|
80
|
-
|
164
|
+
|
81
165
|
##
|
82
166
|
# Returns this document's ancestors
|
83
167
|
def ancestors
|
84
|
-
self.class.
|
168
|
+
self.class.where(:_id.in => parent_ids)
|
85
169
|
end
|
86
|
-
|
170
|
+
|
87
171
|
##
|
88
172
|
# Returns this document's ancestors and itself
|
89
173
|
def ancestors_and_self
|
90
174
|
ancestors + [self]
|
91
175
|
end
|
92
|
-
|
176
|
+
|
93
177
|
##
|
94
178
|
# Is this document an ancestor of the other document?
|
95
179
|
def ancestor_of?(other)
|
96
180
|
other.parent_ids.include?(self.id)
|
97
|
-
end
|
98
|
-
|
181
|
+
end
|
182
|
+
|
99
183
|
##
|
100
184
|
# Returns this document's descendants
|
101
185
|
def descendants
|
102
|
-
self.class.
|
186
|
+
self.class.where(:parent_ids => self.id)
|
103
187
|
end
|
104
|
-
|
188
|
+
|
105
189
|
##
|
106
190
|
# Returns this document's descendants and itself
|
107
191
|
def descendants_and_self
|
108
192
|
[self] + descendants
|
109
193
|
end
|
110
|
-
|
194
|
+
|
111
195
|
##
|
112
196
|
# Is this document a descendant of the other document?
|
113
197
|
def descendant_of?(other)
|
114
198
|
self.parent_ids.include?(other.id)
|
115
199
|
end
|
116
|
-
|
200
|
+
|
117
201
|
##
|
118
202
|
# Returns this document's siblings
|
119
203
|
def siblings
|
120
204
|
siblings_and_self - [self]
|
121
205
|
end
|
122
|
-
|
123
|
-
##
|
206
|
+
|
207
|
+
##
|
124
208
|
# Returns this document's siblings and itself
|
125
209
|
def siblings_and_self
|
126
|
-
self.class.
|
210
|
+
self.class.where(:parent_id => self.parent_id)
|
127
211
|
end
|
128
|
-
|
212
|
+
|
213
|
+
##
|
214
|
+
# Returns all leaves of this document (be careful, currently involves two queries)
|
215
|
+
def leaves
|
216
|
+
self.class.where(:_id.nin => self.class.only(:parent_id).collect(&:parent_id)).and(:parent_ids => self.id)
|
217
|
+
end
|
218
|
+
|
129
219
|
##
|
130
220
|
# Forces rearranging of all children after next save
|
131
221
|
def rearrange_children!
|
132
222
|
@rearrange_children = true
|
133
223
|
end
|
134
|
-
|
224
|
+
|
135
225
|
##
|
136
226
|
# Will the children be rearranged after next save?
|
137
227
|
def rearrange_children?
|
138
228
|
!!@rearrange_children
|
139
229
|
end
|
140
|
-
|
230
|
+
|
141
231
|
private
|
142
|
-
|
232
|
+
|
143
233
|
def rearrange
|
144
234
|
if self.parent_id
|
145
235
|
self.parent_ids = self.class.find(self.parent_id).parent_ids + [self.parent_id]
|
146
236
|
end
|
147
|
-
|
237
|
+
|
148
238
|
rearrange_children! if self.parent_ids_changed?
|
149
|
-
return true
|
150
239
|
end
|
151
|
-
|
240
|
+
|
152
241
|
def rearrange_children
|
153
242
|
@rearrange_children = false
|
154
243
|
self.children.find(:all).each { |c| c.save }
|
155
244
|
end
|
156
245
|
end
|
157
|
-
end
|
246
|
+
end
|
@@ -12,8 +12,8 @@ module Mongoid # :nodoc:
|
|
12
12
|
# == Depth First Traversal
|
13
13
|
#
|
14
14
|
# See http://en.wikipedia.org/wiki/Depth-first_search for a proper description.
|
15
|
-
#
|
16
|
-
# Given a tree like:
|
15
|
+
#
|
16
|
+
# Given a tree like:
|
17
17
|
#
|
18
18
|
# node1:
|
19
19
|
# - node2:
|
@@ -46,7 +46,7 @@ module Mongoid # :nodoc:
|
|
46
46
|
# node1, node2, node3, node4, node5, node6, node7
|
47
47
|
#
|
48
48
|
module Traversal
|
49
|
-
|
49
|
+
|
50
50
|
##
|
51
51
|
# Traverses the tree using the given traversal method (Default is :depth_first)
|
52
52
|
# and passes each document node to the block.
|
@@ -61,16 +61,16 @@ module Mongoid # :nodoc:
|
|
61
61
|
# end
|
62
62
|
def traverse(type = :depth_first, &block)
|
63
63
|
raise "No block given" unless block_given?
|
64
|
-
send("#{type}_traversal", &block)
|
65
|
-
end
|
66
|
-
|
64
|
+
send("#{type}_traversal", &block)
|
65
|
+
end
|
66
|
+
|
67
67
|
private
|
68
|
-
|
68
|
+
|
69
69
|
def depth_first_traversal(&block)
|
70
70
|
block.call(self)
|
71
71
|
self.children.each { |c| c.send(:depth_first_traversal, &block) }
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def breadth_first_traversal(&block)
|
75
75
|
queue = [self]
|
76
76
|
while queue.any? do
|
@@ -79,7 +79,7 @@ module Mongoid # :nodoc:
|
|
79
79
|
queue += node.children
|
80
80
|
end
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
end
|
84
84
|
end
|
85
|
-
end
|
85
|
+
end
|
@@ -1,34 +1,34 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Mongoid::Tree::Traversal do
|
4
|
-
|
4
|
+
|
5
5
|
describe '#traverse' do
|
6
|
-
|
6
|
+
|
7
7
|
subject { Node.new }
|
8
|
-
|
8
|
+
|
9
9
|
it "should require a block" do
|
10
10
|
expect { subject.traverse }.to raise_error(/No block given/)
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
[:depth_first].each do |method|
|
14
14
|
it "should support #{method} traversal" do
|
15
15
|
expect { subject.traverse(method) {} }.to_not raise_error
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
it "should complain about unsupported traversal methods" do
|
20
20
|
expect { subject.traverse('non_existing') {} }.to raise_error
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
it "should default to depth_first traversal" do
|
24
24
|
subject.should_receive(:depth_first_traversal)
|
25
25
|
subject.traverse {}
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
describe 'depth first traversal' do
|
31
|
-
|
31
|
+
|
32
32
|
it "should traverse correctly" do
|
33
33
|
setup_tree <<-ENDTREE
|
34
34
|
node1:
|
@@ -39,38 +39,38 @@ describe Mongoid::Tree::Traversal do
|
|
39
39
|
- node6
|
40
40
|
- node7
|
41
41
|
ENDTREE
|
42
|
-
|
42
|
+
|
43
43
|
result = []
|
44
44
|
node(:node1).traverse(:depth_first) { |node| result << node }
|
45
45
|
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
it "should traverse correctly on merged trees" do
|
49
|
-
|
49
|
+
|
50
50
|
setup_tree <<-ENDTREE
|
51
51
|
- node4:
|
52
52
|
- node5
|
53
53
|
- node6:
|
54
54
|
- node7
|
55
|
-
|
55
|
+
|
56
56
|
- node1:
|
57
57
|
- node2:
|
58
58
|
- node3
|
59
59
|
ENDTREE
|
60
|
-
|
61
|
-
|
60
|
+
|
61
|
+
|
62
62
|
node(:node1).children << node(:node4)
|
63
|
-
|
64
|
-
|
63
|
+
|
64
|
+
|
65
65
|
result = []
|
66
66
|
node(:node1).traverse(:depth_first) { |node| result << node }
|
67
67
|
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
describe 'breadth first traversal' do
|
73
|
-
|
73
|
+
|
74
74
|
it "should traverse correctly" do
|
75
75
|
tree = setup_tree <<-ENDTREE
|
76
76
|
node1:
|
@@ -81,12 +81,12 @@ describe Mongoid::Tree::Traversal do
|
|
81
81
|
- node7
|
82
82
|
- node4
|
83
83
|
ENDTREE
|
84
|
-
|
84
|
+
|
85
85
|
result = []
|
86
86
|
node(:node1).traverse(:breadth_first) { |n| result << n }
|
87
87
|
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
end
|
91
|
-
|
92
|
-
end
|
91
|
+
|
92
|
+
end
|
data/spec/mongoid/tree_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Mongoid::Tree do
|
4
|
-
|
4
|
+
|
5
5
|
it "should reference many children as inverse of parent" do
|
6
6
|
a = Node.associations['children']
|
7
7
|
a.should_not be_nil
|
@@ -10,7 +10,7 @@ describe Mongoid::Tree do
|
|
10
10
|
a.options.foreign_key.should == 'parent_id'
|
11
11
|
a.options.inverse_of.should == :parent
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
it "should be referenced in one parent as inverse of children" do
|
15
15
|
a = Node.associations['parent']
|
16
16
|
a.should_not be_nil
|
@@ -18,16 +18,16 @@ describe Mongoid::Tree do
|
|
18
18
|
a.options.class_name.should == 'Node'
|
19
19
|
a.options.inverse_of.should == :children
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
it "should store parent_ids as Array with [] as default" do
|
23
23
|
f = Node.fields['parent_ids']
|
24
24
|
f.should_not be_nil
|
25
25
|
f.options[:type].should == Array
|
26
26
|
f.options[:default].should == []
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
describe 'when saved' do
|
30
|
-
|
30
|
+
|
31
31
|
before(:each) do
|
32
32
|
setup_tree <<-ENDTREE
|
33
33
|
- root:
|
@@ -38,125 +38,152 @@ describe Mongoid::Tree do
|
|
38
38
|
- other_child
|
39
39
|
ENDTREE
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it "should set the child's parent_id when added to parent's children" do
|
43
43
|
root = Node.create; child = Node.create
|
44
44
|
root.children << child
|
45
45
|
child.parent.should == root
|
46
46
|
child.parent_id.should == root.id
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
it "should set the child's parent_id parent is set on child" do
|
50
50
|
root = Node.create; child = Node.create
|
51
51
|
child.parent = root
|
52
52
|
child.parent.should == root
|
53
53
|
child.parent_id.should == root.id
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
it "should rebuild its parent_ids" do
|
57
57
|
root = Node.create; child = Node.create
|
58
58
|
root.children << child
|
59
59
|
child.parent_ids.should == [root.id]
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
it "should rebuild its children's parent_ids when its own parent_ids changed" do
|
63
63
|
other_root = node(:other_root); child = node(:child); subchild = node(:subchild);
|
64
64
|
other_root.children << child
|
65
65
|
subchild.reload # To get the updated version
|
66
66
|
subchild.parent_ids.should == [other_root.id, child.id]
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
it "should correctly rebuild its descendants' parent_ids when moved into an other subtree" do
|
70
70
|
subchild = node(:subchild); subsubchild = node(:subsubchild); other_child = node(:other_child)
|
71
71
|
other_child.children << subchild
|
72
72
|
subsubchild.reload
|
73
73
|
subsubchild.parent_ids.should == [node(:other_root).id, other_child.id, subchild.id]
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
it "should not rebuild its children's parent_ids when it's not required" do
|
77
77
|
root = node(:root)
|
78
78
|
root.should_not_receive(:rearrange_children)
|
79
79
|
root.save
|
80
80
|
end
|
81
|
-
|
81
|
+
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
describe 'utility methods' do
|
85
|
-
|
85
|
+
|
86
86
|
before(:each) do
|
87
87
|
setup_tree <<-ENDTREE
|
88
|
-
root:
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
- root:
|
89
|
+
- child:
|
90
|
+
- subchild
|
91
|
+
- other_child
|
92
|
+
- other_root
|
92
93
|
ENDTREE
|
93
94
|
end
|
94
|
-
|
95
|
-
describe '.root
|
95
|
+
|
96
|
+
describe '.root' do
|
97
|
+
it "should return the first root document" do
|
98
|
+
Node.root.should == node(:root)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '.roots' do
|
103
|
+
it "should return all root documents" do
|
104
|
+
Node.roots.to_a.should == [node(:root), node(:other_root)]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '.leaves' do
|
109
|
+
it "should return all leaf documents" do
|
110
|
+
Node.leaves.to_a.should =~ [node(:subchild), node(:other_child), node(:other_root)]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#root?' do
|
96
115
|
it "should return true for root documents" do
|
97
116
|
node(:root).should be_root
|
98
117
|
end
|
99
|
-
|
118
|
+
|
100
119
|
it "should return false for non-root documents" do
|
101
120
|
node(:child).should_not be_root
|
102
121
|
end
|
103
122
|
end
|
104
|
-
|
105
|
-
describe '
|
123
|
+
|
124
|
+
describe '#leaf?' do
|
106
125
|
it "should return true for leaf documents" do
|
107
126
|
node(:subchild).should be_leaf
|
108
127
|
node(:other_child).should be_leaf
|
109
128
|
Node.new.should be_leaf
|
110
129
|
end
|
111
|
-
|
130
|
+
|
112
131
|
it "should return false for non-leaf documents" do
|
113
132
|
node(:child).should_not be_leaf
|
114
133
|
node(:root).should_not be_leaf
|
115
134
|
end
|
116
135
|
end
|
117
|
-
|
118
|
-
describe '
|
136
|
+
|
137
|
+
describe '#depth' do
|
138
|
+
it "should return the depth of this document" do
|
139
|
+
node(:root).depth.should == 0
|
140
|
+
node(:child).depth.should == 1
|
141
|
+
node(:subchild).depth.should == 2
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '#root' do
|
119
146
|
it "should return the root for this document" do
|
120
147
|
node(:subchild).root.should == node(:root)
|
121
148
|
end
|
122
149
|
end
|
123
|
-
|
150
|
+
|
124
151
|
describe 'ancestors' do
|
125
|
-
it "
|
152
|
+
it "#ancestors should return the documents ancestors" do
|
126
153
|
node(:subchild).ancestors.to_a.should == [node(:root), node(:child)]
|
127
154
|
end
|
128
|
-
|
129
|
-
it "
|
155
|
+
|
156
|
+
it "#ancestors_and_self should return the documents ancestors and itself" do
|
130
157
|
node(:subchild).ancestors_and_self.to_a.should == [node(:root), node(:child), node(:subchild)]
|
131
158
|
end
|
132
|
-
|
133
|
-
describe '
|
159
|
+
|
160
|
+
describe '#ancestor_of?' do
|
134
161
|
it "should return true for ancestors" do
|
135
162
|
node(:child).should be_ancestor_of(node(:subchild))
|
136
163
|
end
|
137
|
-
|
164
|
+
|
138
165
|
it "should return false for non-ancestors" do
|
139
166
|
node(:other_child).should_not be_ancestor_of(node(:subchild))
|
140
167
|
end
|
141
168
|
end
|
142
169
|
end
|
143
|
-
|
170
|
+
|
144
171
|
describe 'descendants' do
|
145
|
-
it "
|
172
|
+
it "#descendants should return the documents descendants" do
|
146
173
|
node(:root).descendants.to_a.should =~ [node(:child), node(:other_child), node(:subchild)]
|
147
174
|
end
|
148
|
-
|
149
|
-
it "
|
175
|
+
|
176
|
+
it "#descendants_and_self should return the documents descendants and itself" do
|
150
177
|
node(:root).descendants_and_self.to_a.should =~ [node(:root), node(:child), node(:other_child), node(:subchild)]
|
151
178
|
end
|
152
|
-
|
153
|
-
describe '
|
179
|
+
|
180
|
+
describe '#descendant_of?' do
|
154
181
|
it "should return true for descendants" do
|
155
182
|
subchild = node(:subchild)
|
156
183
|
subchild.should be_descendant_of(node(:child))
|
157
184
|
subchild.should be_descendant_of(node(:root))
|
158
185
|
end
|
159
|
-
|
186
|
+
|
160
187
|
it "should return false for non-descendants" do
|
161
188
|
node(:subchild).should_not be_descendant_of(node(:other_child))
|
162
189
|
end
|
@@ -164,15 +191,60 @@ describe Mongoid::Tree do
|
|
164
191
|
end
|
165
192
|
|
166
193
|
describe 'siblings' do
|
167
|
-
it "
|
194
|
+
it "#siblings should return the documents siblings" do
|
168
195
|
node(:child).siblings.to_a.should == [node(:other_child)]
|
169
196
|
end
|
170
|
-
|
171
|
-
it "
|
197
|
+
|
198
|
+
it "#siblings_and_self should return the documents siblings and itself" do
|
172
199
|
node(:child).siblings_and_self.to_a.should == [node(:child), node(:other_child)]
|
173
200
|
end
|
174
201
|
end
|
175
|
-
|
202
|
+
|
203
|
+
describe '#leaves' do
|
204
|
+
it "should return this documents leaves" do
|
205
|
+
node(:root).leaves.to_a.should =~ [node(:other_child), node(:subchild)]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
describe 'callbacks' do
|
212
|
+
|
213
|
+
after(:each) do
|
214
|
+
Node.reset_callbacks(:rearrange)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should provide a before_rearrange callback" do
|
218
|
+
Node.should respond_to :before_rearrange
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should provida an after_rearrange callback" do
|
222
|
+
Node.should respond_to :after_rearrange
|
223
|
+
end
|
224
|
+
|
225
|
+
describe 'before rearrange callback' do
|
226
|
+
|
227
|
+
it "should be called before the document is rearranged" do
|
228
|
+
Node.before_rearrange :callback
|
229
|
+
node = Node.new
|
230
|
+
node.should_receive(:callback).ordered
|
231
|
+
node.should_receive(:rearrange).ordered
|
232
|
+
node.save
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
describe 'after rearrange callback' do
|
238
|
+
|
239
|
+
it "should be called after the document is rearranged" do
|
240
|
+
Node.after_rearrange :callback
|
241
|
+
node = Node.new
|
242
|
+
node.should_receive(:rearrange).ordered
|
243
|
+
node.should_receive(:callback).ordered
|
244
|
+
node.save
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
176
249
|
end
|
177
|
-
|
178
|
-
end
|
250
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
module Mongoid::Tree::TreeMacros
|
4
|
-
|
4
|
+
|
5
5
|
def setup_tree(tree)
|
6
6
|
create_tree(YAML.load(tree))
|
7
7
|
end
|
8
|
-
|
9
|
-
def node(name)
|
8
|
+
|
9
|
+
def node(name)
|
10
10
|
@nodes[name].reload
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def print_tree(node, print_ids = false, depth = 0)
|
14
14
|
print ' ' * depth
|
15
15
|
print '- ' unless depth == 0
|
@@ -19,28 +19,28 @@ module Mongoid::Tree::TreeMacros
|
|
19
19
|
print "\n"
|
20
20
|
node.children.each { |c| print_tree(c, print_ids, depth + 1) }
|
21
21
|
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
25
|
def create_tree(object)
|
26
26
|
case object
|
27
27
|
when String: return create_node(object)
|
28
28
|
when Array: object.each { |tree| create_tree(tree) }
|
29
|
-
when Hash:
|
29
|
+
when Hash:
|
30
30
|
name, children = object.first
|
31
31
|
node = create_node(name)
|
32
32
|
children.each { |c| node.children << create_tree(c) }
|
33
33
|
return node
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def create_node(name)
|
38
38
|
@nodes ||= HashWithIndifferentAccess.new
|
39
39
|
@nodes[name] = Node.create(:name => name)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
end
|
43
43
|
|
44
44
|
RSpec.configure do |config|
|
45
45
|
config.include Mongoid::Tree::TreeMacros
|
46
|
-
end
|
46
|
+
end
|
data/spec/support/models/node.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Benedikt Deicke
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-07-
|
18
|
+
date: 2010-07-27 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|