mongoid-tree 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +26 -0
- data/lib/mongoid/tree.rb +157 -0
- data/lib/mongoid/tree/traversal.rb +85 -0
- data/spec/mongoid/tree/traversal_spec.rb +92 -0
- data/spec/mongoid/tree_spec.rb +178 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/macros/tree_macros.rb +46 -0
- data/spec/support/models/node.rb +7 -0
- metadata +148 -0
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Benedikt Deicke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= mongoid-tree
|
2
|
+
|
3
|
+
A tree structure for Mongoid documents using the materialized path pattern
|
4
|
+
|
5
|
+
== Requirements
|
6
|
+
|
7
|
+
* mongoid (>= 2.0.0.beta9)
|
8
|
+
|
9
|
+
== Install
|
10
|
+
|
11
|
+
To install mongoid_tree, simply add it to your Gemfile:
|
12
|
+
|
13
|
+
gem "mongoid-tree"
|
14
|
+
|
15
|
+
In order to get the latest development version of mongoid-tree:
|
16
|
+
|
17
|
+
gem "mongoid-tree" :git => "git://github.com/benedikt/mongoid-tree"
|
18
|
+
|
19
|
+
You might want to add the <tt>:require => 'mongoid/tree'</tt> option as well and finally run
|
20
|
+
|
21
|
+
bundle install
|
22
|
+
|
23
|
+
== Usage
|
24
|
+
|
25
|
+
Read the API documentation at http://benedikt.github.com/mongoid-tree and take a look at the Mongoid::Tree module
|
26
|
+
|
27
|
+
require 'mongoid/tree'
|
28
|
+
|
29
|
+
class Node
|
30
|
+
include Mongoid::Document
|
31
|
+
include Mongoid::Tree
|
32
|
+
end
|
33
|
+
|
34
|
+
== Known issues
|
35
|
+
|
36
|
+
See http://github.com/benedikt/mongoid-tree/issues
|
37
|
+
|
38
|
+
== Repository
|
39
|
+
|
40
|
+
See http://github.com/benedikt/mongoid-tree and feel free to fork it!
|
41
|
+
|
42
|
+
== Copyright
|
43
|
+
|
44
|
+
Copyright (c) 2010 Benedikt Deicke. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'hanna/rdoctask'
|
3
|
+
|
4
|
+
spec = Gem::Specification.load("mongoid-tree.gemspec")
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
Rake::RDocTask.new do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'doc'
|
12
|
+
rdoc.title = "#{spec.name} #{spec.version}"
|
13
|
+
rdoc.options += spec.rdoc_options
|
14
|
+
rdoc.rdoc_files.include(spec.extra_rdoc_files)
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Build the .gem file"
|
19
|
+
task :build do
|
20
|
+
system "gem build #{spec.name}.gemspec"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Push the .gem file to rubygems.org"
|
24
|
+
task :release => :build do
|
25
|
+
system "gem push #{spec.name}-#{spec.version}.gem"
|
26
|
+
end
|
data/lib/mongoid/tree.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'mongoid/tree/traversal'
|
2
|
+
|
3
|
+
module Mongoid # :nodoc:
|
4
|
+
##
|
5
|
+
# = Mongoid::Tree
|
6
|
+
#
|
7
|
+
# This module extends any Mongoid document with tree functionality.
|
8
|
+
#
|
9
|
+
# == Usage
|
10
|
+
#
|
11
|
+
# Simply include the module in any Mongoid document:
|
12
|
+
#
|
13
|
+
# class Node
|
14
|
+
# include Mongoid::Document
|
15
|
+
# include Mongoid::Tree
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# === Using the tree structure
|
19
|
+
#
|
20
|
+
# Each document references many children. You can access them using the <tt>#children</tt> method.
|
21
|
+
#
|
22
|
+
# node = Node.create
|
23
|
+
# node.children.create
|
24
|
+
# node.children.count # => 1
|
25
|
+
#
|
26
|
+
# Every document references one parent (unless it's a root document).
|
27
|
+
#
|
28
|
+
# node = Node.create
|
29
|
+
# node.parent # => nil
|
30
|
+
# node.children.create
|
31
|
+
# node.children.first.parent # => node
|
32
|
+
#
|
33
|
+
module Tree
|
34
|
+
extend ActiveSupport::Concern
|
35
|
+
|
36
|
+
include Traversal
|
37
|
+
|
38
|
+
included do
|
39
|
+
reference_many :children, :class_name => self.name, :foreign_key => :parent_id, :inverse_of => :parent
|
40
|
+
referenced_in :parent, :class_name => self.name, :inverse_of => :children
|
41
|
+
|
42
|
+
field :parent_ids, :type => Array, :default => []
|
43
|
+
|
44
|
+
set_callback :validation, :before, :rearrange
|
45
|
+
set_callback :save, :after, :rearrange_children, :if => :rearrange_children?
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# :method: children
|
50
|
+
# Returns a list of the document's children. It's a <tt>reference_many</tt> association.
|
51
|
+
# (Generated by Mongoid)
|
52
|
+
|
53
|
+
##
|
54
|
+
# :method: parent
|
55
|
+
# Returns the document's parent (unless it's a root document). It's a <tt>referenced_in</tt> association.
|
56
|
+
# (Generated by Mongoid)
|
57
|
+
|
58
|
+
##
|
59
|
+
# :method: parent_ids
|
60
|
+
# Returns a list of the document's parent_ids, starting with the root node.
|
61
|
+
# (Generated by Mongoid)
|
62
|
+
|
63
|
+
##
|
64
|
+
# Is this document a root node (has no parent)?
|
65
|
+
def root?
|
66
|
+
parent_id.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Is this document a leaf node (has no children)?
|
71
|
+
def leaf?
|
72
|
+
children.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Returns this document's root node
|
77
|
+
def root
|
78
|
+
self.class.find(parent_ids.first)
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Returns this document's ancestors
|
83
|
+
def ancestors
|
84
|
+
self.class.find(:conditions => { :_id.in => parent_ids })
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Returns this document's ancestors and itself
|
89
|
+
def ancestors_and_self
|
90
|
+
ancestors + [self]
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Is this document an ancestor of the other document?
|
95
|
+
def ancestor_of?(other)
|
96
|
+
other.parent_ids.include?(self.id)
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Returns this document's descendants
|
101
|
+
def descendants
|
102
|
+
self.class.find(:conditions => { :parent_ids => self.id })
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Returns this document's descendants and itself
|
107
|
+
def descendants_and_self
|
108
|
+
[self] + descendants
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Is this document a descendant of the other document?
|
113
|
+
def descendant_of?(other)
|
114
|
+
self.parent_ids.include?(other.id)
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Returns this document's siblings
|
119
|
+
def siblings
|
120
|
+
siblings_and_self - [self]
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Returns this document's siblings and itself
|
125
|
+
def siblings_and_self
|
126
|
+
self.class.find(:conditions => { :parent_id => self.parent_id })
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Forces rearranging of all children after next save
|
131
|
+
def rearrange_children!
|
132
|
+
@rearrange_children = true
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# Will the children be rearranged after next save?
|
137
|
+
def rearrange_children?
|
138
|
+
!!@rearrange_children
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def rearrange
|
144
|
+
if self.parent_id
|
145
|
+
self.parent_ids = self.class.find(self.parent_id).parent_ids + [self.parent_id]
|
146
|
+
end
|
147
|
+
|
148
|
+
rearrange_children! if self.parent_ids_changed?
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
|
152
|
+
def rearrange_children
|
153
|
+
@rearrange_children = false
|
154
|
+
self.children.find(:all).each { |c| c.save }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Mongoid # :nodoc:
|
2
|
+
module Tree
|
3
|
+
##
|
4
|
+
# = Mongoid::Tree::Traversal
|
5
|
+
#
|
6
|
+
# Mongoid::Tree provides a #traverse method to walk through the tree.
|
7
|
+
# It supports these traversal methods:
|
8
|
+
#
|
9
|
+
# * depth_first
|
10
|
+
# * breadth_first
|
11
|
+
#
|
12
|
+
# == Depth First Traversal
|
13
|
+
#
|
14
|
+
# See http://en.wikipedia.org/wiki/Depth-first_search for a proper description.
|
15
|
+
#
|
16
|
+
# Given a tree like:
|
17
|
+
#
|
18
|
+
# node1:
|
19
|
+
# - node2:
|
20
|
+
# - node3
|
21
|
+
# - node4:
|
22
|
+
# - node5
|
23
|
+
# - node6
|
24
|
+
# - node7
|
25
|
+
#
|
26
|
+
# Traversing the tree using depth first traversal would visit each node in this order:
|
27
|
+
#
|
28
|
+
# node1, node2, node3, node4, node5, node6, node7
|
29
|
+
#
|
30
|
+
# == Breadth First Traversal
|
31
|
+
#
|
32
|
+
# See http://en.wikipedia.org/wiki/Breadth-first_search for a proper description.
|
33
|
+
#
|
34
|
+
# Given a tree like:
|
35
|
+
#
|
36
|
+
# node1:
|
37
|
+
# - node2:
|
38
|
+
# - node5
|
39
|
+
# - node3:
|
40
|
+
# - node6
|
41
|
+
# - node7
|
42
|
+
# - node4
|
43
|
+
#
|
44
|
+
# Traversing the tree using breadth first traversal would visit each node in this order:
|
45
|
+
#
|
46
|
+
# node1, node2, node3, node4, node5, node6, node7
|
47
|
+
#
|
48
|
+
module Traversal
|
49
|
+
|
50
|
+
##
|
51
|
+
# Traverses the tree using the given traversal method (Default is :depth_first)
|
52
|
+
# and passes each document node to the block.
|
53
|
+
#
|
54
|
+
# See Mongoid::Tree::Traversal for available traversal methods.
|
55
|
+
#
|
56
|
+
# Example:
|
57
|
+
#
|
58
|
+
# results = []
|
59
|
+
# root.traverse(:depth_first) do |node|
|
60
|
+
# results << node
|
61
|
+
# end
|
62
|
+
def traverse(type = :depth_first, &block)
|
63
|
+
raise "No block given" unless block_given?
|
64
|
+
send("#{type}_traversal", &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def depth_first_traversal(&block)
|
70
|
+
block.call(self)
|
71
|
+
self.children.each { |c| c.send(:depth_first_traversal, &block) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def breadth_first_traversal(&block)
|
75
|
+
queue = [self]
|
76
|
+
while queue.any? do
|
77
|
+
node = queue.shift
|
78
|
+
block.call(node)
|
79
|
+
queue += node.children
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongoid::Tree::Traversal do
|
4
|
+
|
5
|
+
describe '#traverse' do
|
6
|
+
|
7
|
+
subject { Node.new }
|
8
|
+
|
9
|
+
it "should require a block" do
|
10
|
+
expect { subject.traverse }.to raise_error(/No block given/)
|
11
|
+
end
|
12
|
+
|
13
|
+
[:depth_first].each do |method|
|
14
|
+
it "should support #{method} traversal" do
|
15
|
+
expect { subject.traverse(method) {} }.to_not raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should complain about unsupported traversal methods" do
|
20
|
+
expect { subject.traverse('non_existing') {} }.to raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should default to depth_first traversal" do
|
24
|
+
subject.should_receive(:depth_first_traversal)
|
25
|
+
subject.traverse {}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'depth first traversal' do
|
31
|
+
|
32
|
+
it "should traverse correctly" do
|
33
|
+
setup_tree <<-ENDTREE
|
34
|
+
node1:
|
35
|
+
- node2:
|
36
|
+
- node3
|
37
|
+
- node4:
|
38
|
+
- node5
|
39
|
+
- node6
|
40
|
+
- node7
|
41
|
+
ENDTREE
|
42
|
+
|
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
|
47
|
+
|
48
|
+
it "should traverse correctly on merged trees" do
|
49
|
+
|
50
|
+
setup_tree <<-ENDTREE
|
51
|
+
- node4:
|
52
|
+
- node5
|
53
|
+
- node6:
|
54
|
+
- node7
|
55
|
+
|
56
|
+
- node1:
|
57
|
+
- node2:
|
58
|
+
- node3
|
59
|
+
ENDTREE
|
60
|
+
|
61
|
+
|
62
|
+
node(:node1).children << node(:node4)
|
63
|
+
|
64
|
+
|
65
|
+
result = []
|
66
|
+
node(:node1).traverse(:depth_first) { |node| result << node }
|
67
|
+
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'breadth first traversal' do
|
73
|
+
|
74
|
+
it "should traverse correctly" do
|
75
|
+
tree = setup_tree <<-ENDTREE
|
76
|
+
node1:
|
77
|
+
- node2:
|
78
|
+
- node5
|
79
|
+
- node3:
|
80
|
+
- node6
|
81
|
+
- node7
|
82
|
+
- node4
|
83
|
+
ENDTREE
|
84
|
+
|
85
|
+
result = []
|
86
|
+
node(:node1).traverse(:breadth_first) { |n| result << n }
|
87
|
+
result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongoid::Tree do
|
4
|
+
|
5
|
+
it "should reference many children as inverse of parent" do
|
6
|
+
a = Node.associations['children']
|
7
|
+
a.should_not be_nil
|
8
|
+
a.association.should == Mongoid::Associations::ReferencesMany
|
9
|
+
a.options.class_name.should == 'Node'
|
10
|
+
a.options.foreign_key.should == 'parent_id'
|
11
|
+
a.options.inverse_of.should == :parent
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be referenced in one parent as inverse of children" do
|
15
|
+
a = Node.associations['parent']
|
16
|
+
a.should_not be_nil
|
17
|
+
a.association.should == Mongoid::Associations::ReferencedIn
|
18
|
+
a.options.class_name.should == 'Node'
|
19
|
+
a.options.inverse_of.should == :children
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should store parent_ids as Array with [] as default" do
|
23
|
+
f = Node.fields['parent_ids']
|
24
|
+
f.should_not be_nil
|
25
|
+
f.options[:type].should == Array
|
26
|
+
f.options[:default].should == []
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'when saved' do
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
setup_tree <<-ENDTREE
|
33
|
+
- root:
|
34
|
+
- child:
|
35
|
+
- subchild:
|
36
|
+
- subsubchild
|
37
|
+
- other_root:
|
38
|
+
- other_child
|
39
|
+
ENDTREE
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should set the child's parent_id when added to parent's children" do
|
43
|
+
root = Node.create; child = Node.create
|
44
|
+
root.children << child
|
45
|
+
child.parent.should == root
|
46
|
+
child.parent_id.should == root.id
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should set the child's parent_id parent is set on child" do
|
50
|
+
root = Node.create; child = Node.create
|
51
|
+
child.parent = root
|
52
|
+
child.parent.should == root
|
53
|
+
child.parent_id.should == root.id
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should rebuild its parent_ids" do
|
57
|
+
root = Node.create; child = Node.create
|
58
|
+
root.children << child
|
59
|
+
child.parent_ids.should == [root.id]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should rebuild its children's parent_ids when its own parent_ids changed" do
|
63
|
+
other_root = node(:other_root); child = node(:child); subchild = node(:subchild);
|
64
|
+
other_root.children << child
|
65
|
+
subchild.reload # To get the updated version
|
66
|
+
subchild.parent_ids.should == [other_root.id, child.id]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should correctly rebuild its descendants' parent_ids when moved into an other subtree" do
|
70
|
+
subchild = node(:subchild); subsubchild = node(:subsubchild); other_child = node(:other_child)
|
71
|
+
other_child.children << subchild
|
72
|
+
subsubchild.reload
|
73
|
+
subsubchild.parent_ids.should == [node(:other_root).id, other_child.id, subchild.id]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not rebuild its children's parent_ids when it's not required" do
|
77
|
+
root = node(:root)
|
78
|
+
root.should_not_receive(:rearrange_children)
|
79
|
+
root.save
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'utility methods' do
|
85
|
+
|
86
|
+
before(:each) do
|
87
|
+
setup_tree <<-ENDTREE
|
88
|
+
root:
|
89
|
+
- child:
|
90
|
+
- subchild
|
91
|
+
- other_child
|
92
|
+
ENDTREE
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '.root?' do
|
96
|
+
it "should return true for root documents" do
|
97
|
+
node(:root).should be_root
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return false for non-root documents" do
|
101
|
+
node(:child).should_not be_root
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '.leaf?' do
|
106
|
+
it "should return true for leaf documents" do
|
107
|
+
node(:subchild).should be_leaf
|
108
|
+
node(:other_child).should be_leaf
|
109
|
+
Node.new.should be_leaf
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should return false for non-leaf documents" do
|
113
|
+
node(:child).should_not be_leaf
|
114
|
+
node(:root).should_not be_leaf
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '.root' do
|
119
|
+
it "should return the root for this document" do
|
120
|
+
node(:subchild).root.should == node(:root)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'ancestors' do
|
125
|
+
it ".ancestors should return the documents ancestors" do
|
126
|
+
node(:subchild).ancestors.to_a.should == [node(:root), node(:child)]
|
127
|
+
end
|
128
|
+
|
129
|
+
it ".ancestors_and_self should return the documents ancestors and itself" do
|
130
|
+
node(:subchild).ancestors_and_self.to_a.should == [node(:root), node(:child), node(:subchild)]
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '.ancestor_of?' do
|
134
|
+
it "should return true for ancestors" do
|
135
|
+
node(:child).should be_ancestor_of(node(:subchild))
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should return false for non-ancestors" do
|
139
|
+
node(:other_child).should_not be_ancestor_of(node(:subchild))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'descendants' do
|
145
|
+
it ".descendants should return the documents descendants" do
|
146
|
+
node(:root).descendants.to_a.should =~ [node(:child), node(:other_child), node(:subchild)]
|
147
|
+
end
|
148
|
+
|
149
|
+
it ".descendants_and_self should return the documents descendants and itself" do
|
150
|
+
node(:root).descendants_and_self.to_a.should =~ [node(:root), node(:child), node(:other_child), node(:subchild)]
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '.descendant_of?' do
|
154
|
+
it "should return true for descendants" do
|
155
|
+
subchild = node(:subchild)
|
156
|
+
subchild.should be_descendant_of(node(:child))
|
157
|
+
subchild.should be_descendant_of(node(:root))
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should return false for non-descendants" do
|
161
|
+
node(:subchild).should_not be_descendant_of(node(:other_child))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe 'siblings' do
|
167
|
+
it ".siblings should return the documents siblings" do
|
168
|
+
node(:child).siblings.to_a.should == [node(:other_child)]
|
169
|
+
end
|
170
|
+
|
171
|
+
it ".siblings_and_self should return the documents siblings and itself" do
|
172
|
+
node(:child).siblings_and_self.to_a.should == [node(:child), node(:other_child)]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'mongoid'
|
5
|
+
require 'mongoid/tree'
|
6
|
+
|
7
|
+
require 'rspec'
|
8
|
+
|
9
|
+
Mongoid.configure do |config|
|
10
|
+
config.master = Mongo::Connection.new.db('mongoid_tree_test')
|
11
|
+
config.allow_dynamic_fields = false
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.mock_with :rspec
|
18
|
+
config.after :each do
|
19
|
+
Mongoid.master.collections.reject { |c| c.name =~ /^system\./ }.each(&:drop)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Mongoid::Tree::TreeMacros
|
4
|
+
|
5
|
+
def setup_tree(tree)
|
6
|
+
create_tree(YAML.load(tree))
|
7
|
+
end
|
8
|
+
|
9
|
+
def node(name)
|
10
|
+
@nodes[name].reload
|
11
|
+
end
|
12
|
+
|
13
|
+
def print_tree(node, print_ids = false, depth = 0)
|
14
|
+
print ' ' * depth
|
15
|
+
print '- ' unless depth == 0
|
16
|
+
print node.name
|
17
|
+
print " (#{node.id})" if print_ids
|
18
|
+
print ':' if node.children.any?
|
19
|
+
print "\n"
|
20
|
+
node.children.each { |c| print_tree(c, print_ids, depth + 1) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def create_tree(object)
|
26
|
+
case object
|
27
|
+
when String: return create_node(object)
|
28
|
+
when Array: object.each { |tree| create_tree(tree) }
|
29
|
+
when Hash:
|
30
|
+
name, children = object.first
|
31
|
+
node = create_node(name)
|
32
|
+
children.each { |c| node.children << create_tree(c) }
|
33
|
+
return node
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_node(name)
|
38
|
+
@nodes ||= HashWithIndifferentAccess.new
|
39
|
+
@nodes[name] = Node.create(:name => name)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
RSpec.configure do |config|
|
45
|
+
config.include Mongoid::Tree::TreeMacros
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Benedikt Deicke
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-26 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: mongoid
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: -1848230043
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
- beta9
|
35
|
+
version: 2.0.0.beta9
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 62196423
|
47
|
+
segments:
|
48
|
+
- 2
|
49
|
+
- 0
|
50
|
+
- 0
|
51
|
+
- beta
|
52
|
+
- 18
|
53
|
+
version: 2.0.0.beta.18
|
54
|
+
type: :development
|
55
|
+
version_requirements: *id002
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: autotest
|
58
|
+
prerelease: false
|
59
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 55
|
65
|
+
segments:
|
66
|
+
- 4
|
67
|
+
- 3
|
68
|
+
- 2
|
69
|
+
version: 4.3.2
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id003
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: hanna
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
hash: 3
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
- 1
|
84
|
+
- 12
|
85
|
+
version: 0.1.12
|
86
|
+
type: :development
|
87
|
+
version_requirements: *id004
|
88
|
+
description: A tree structure for Mongoid documents using the materialized path pattern
|
89
|
+
email:
|
90
|
+
- benedikt@synatic.net
|
91
|
+
executables: []
|
92
|
+
|
93
|
+
extensions: []
|
94
|
+
|
95
|
+
extra_rdoc_files:
|
96
|
+
- README.rdoc
|
97
|
+
- LICENSE
|
98
|
+
files:
|
99
|
+
- lib/mongoid/tree/traversal.rb
|
100
|
+
- lib/mongoid/tree.rb
|
101
|
+
- spec/mongoid/tree/traversal_spec.rb
|
102
|
+
- spec/mongoid/tree_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- spec/support/macros/tree_macros.rb
|
105
|
+
- spec/support/models/node.rb
|
106
|
+
- LICENSE
|
107
|
+
- README.rdoc
|
108
|
+
- Rakefile
|
109
|
+
- Gemfile
|
110
|
+
- .rspec
|
111
|
+
has_rdoc: true
|
112
|
+
homepage: http://github.com/benedikt/mongoid-tree
|
113
|
+
licenses: []
|
114
|
+
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options:
|
117
|
+
- --main
|
118
|
+
- README.rdoc
|
119
|
+
- --charset=UTF-8
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
hash: 3
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
hash: 3
|
137
|
+
segments:
|
138
|
+
- 0
|
139
|
+
version: "0"
|
140
|
+
requirements: []
|
141
|
+
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 1.3.7
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: A tree structure for Mongoid documents
|
147
|
+
test_files: []
|
148
|
+
|