mongoid-tree 0.1.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/.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
|
+
|