mully-mongo_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/Manifest +4 -0
- data/README.rdoc +47 -0
- data/Rakefile +14 -0
- data/lib/mongo_tree.rb +241 -0
- data/mongo_tree.gemspec +31 -0
- metadata +63 -0
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
= MongoTree
|
2
|
+
|
3
|
+
This is a simple way to add parent / child relationships to a Rails model that uses MongoDB and the MongoRecord plugin (not the Mongo ActiveRecord plugin.)
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
Gem:
|
8
|
+
|
9
|
+
sudo gem install mongo_tree
|
10
|
+
|
11
|
+
Plugin:
|
12
|
+
|
13
|
+
script/plugin install git://github.com/mully/mongo_tree.git
|
14
|
+
|
15
|
+
== Usage
|
16
|
+
|
17
|
+
To use mongo tree, simply add the word "mongo_tree" to your model.
|
18
|
+
|
19
|
+
class Taxon < MongoRecord::Base
|
20
|
+
mongo_Tree
|
21
|
+
end
|
22
|
+
|
23
|
+
Add Child:
|
24
|
+
@root = Taxon.new({:name=>"ROOT", :content=>"ROOT content"})
|
25
|
+
@root.save
|
26
|
+
@child1 = Taxon.new({:name=>"Child", :content=>"Child content"})
|
27
|
+
@root << @child1
|
28
|
+
@grandchild = Taxon.new({:name=>"GrandChild", :content=>"Grand content"})
|
29
|
+
@child1 << @grandchild
|
30
|
+
|
31
|
+
Access Children:
|
32
|
+
@root.children #access all first level childrent
|
33
|
+
@root.descendants #access all descendants (children, grandchildren, etc)
|
34
|
+
@root.parent #parent object
|
35
|
+
@root.ancestors #all ancestors (parent, grandparents, etc)
|
36
|
+
|
37
|
+
Delete Children:
|
38
|
+
@root.remove!(@child1) #remove child from root. the child will be resaved in it's own root node.
|
39
|
+
|
40
|
+
== References
|
41
|
+
|
42
|
+
http://mongodb.org
|
43
|
+
http://github.com/mongodb/mongo-activerecord-ruby/tree/master
|
44
|
+
http://www.koders.com/ruby/fid995C0ABC8DD9B624D7C4E4D9EE319F374E4CA7FA.aspx?s=cdef%3Atree#L11
|
45
|
+
http://wiki.apache.org/couchdb/How_to_store_hierarchical_data
|
46
|
+
|
47
|
+
Copyright (c) 2009 Squeejee, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('mongo_tree', '0.1.0') do |p|
|
6
|
+
p.description = "Add hierarchy tree functionality to MongoRecord (MongoDB) models."
|
7
|
+
p.url = "http://github.com/mully/mongo_tree"
|
8
|
+
p.author = "Jim Mulhollnad"
|
9
|
+
p.email = "jim@squeejee.com"
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
11
|
+
p.development_dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/lib/mongo_tree.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
module MongoTree
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def mongo_tree(options={})
|
8
|
+
fields :name
|
9
|
+
fields :content
|
10
|
+
fields :parents
|
11
|
+
|
12
|
+
fields :created_at
|
13
|
+
fields :updated_at
|
14
|
+
|
15
|
+
class_eval <<-EOV
|
16
|
+
include MongoTree::InstanceMethods
|
17
|
+
EOV
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
|
24
|
+
@@fieldSep = '|'
|
25
|
+
@@recordSep = "\n"
|
26
|
+
|
27
|
+
def initialize(options={})
|
28
|
+
super
|
29
|
+
self.set_as_root!
|
30
|
+
|
31
|
+
if self.parents.nil?
|
32
|
+
self.parents = []
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
s = size()
|
38
|
+
"Node ID: #{@name} Content: #{@content} Parent: " +
|
39
|
+
(is_root?() ? "ROOT" : "#{@parent.name}") +
|
40
|
+
" Children: #{children.length}" +
|
41
|
+
" Total Nodes: #{s}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Convenience synonym for Tree#add method.
|
45
|
+
# This method allows a convenient method to add
|
46
|
+
# children hierarchies in the tree.
|
47
|
+
# E.g. root << child << grand_child
|
48
|
+
def <<(child)
|
49
|
+
add(child)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds the specified child node to the receiver node.
|
53
|
+
# The child node's parent is set to be the receiver.
|
54
|
+
# The child is added as the last child in the current
|
55
|
+
# list of children for the receiver node.
|
56
|
+
def add(child)
|
57
|
+
raise "Child already added" if child.parents.include?(self.id)
|
58
|
+
child.parents = []
|
59
|
+
child.parents << self.parents
|
60
|
+
child.parents << self.id
|
61
|
+
child.parents.flatten!
|
62
|
+
child.save
|
63
|
+
return child
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# Removes the specified child node from the receiver node.
|
68
|
+
# The removed children nodes are orphaned but available
|
69
|
+
# if an alternate reference exists.
|
70
|
+
# Returns the child node.
|
71
|
+
def remove!(child)
|
72
|
+
# Make sure child belongs to the object trying to remove it
|
73
|
+
child = self.children.select {|c| c.id == child.id}.first
|
74
|
+
unless child.nil?
|
75
|
+
old_parents = child.parents
|
76
|
+
child.parents = []
|
77
|
+
child.save
|
78
|
+
# Update the parent tree of all this object's children
|
79
|
+
child.descendants.each do |descendant, level|
|
80
|
+
descendant.parents = descendant.parents - old_parents
|
81
|
+
descendant.save
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
return child
|
86
|
+
end
|
87
|
+
|
88
|
+
# Removes this node from its parent. If this is the root node,
|
89
|
+
# then does nothing.
|
90
|
+
def remove_from_parent!
|
91
|
+
parent.remove!(self) unless is_root?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Removes all children from the receiver node.
|
95
|
+
def remove_all!
|
96
|
+
for child in children
|
97
|
+
child.remove_from_parent!
|
98
|
+
end
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Indicates whether this node has any associated content.
|
103
|
+
def has_content?
|
104
|
+
@content != nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Indicates whether this node is a root node. Note that
|
108
|
+
# orphaned children will also be reported as root nodes.
|
109
|
+
def is_root?
|
110
|
+
parents == []
|
111
|
+
end
|
112
|
+
|
113
|
+
# Indicates whether this node has any immediate child nodes.
|
114
|
+
def has_children?
|
115
|
+
children.length != 0
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns a multi-dimensional array of children objects and the level
|
119
|
+
# where level is the depth in the hierarchy.
|
120
|
+
def descendants(max_level=nil)
|
121
|
+
children=[]
|
122
|
+
self.class.find(:all).each do |obj|
|
123
|
+
if obj.parents.include?(self.id)
|
124
|
+
level = obj.parents.reverse.index(self.id)
|
125
|
+
if max_level.nil?
|
126
|
+
children << [obj, level]
|
127
|
+
else
|
128
|
+
children << [obj, level] if level <= max_level
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
return children
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns all children of an object
|
136
|
+
def children
|
137
|
+
descendants(0).map {|c| c[0]}
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the root for this node.
|
141
|
+
def root
|
142
|
+
self.parents.blank? ? self : self.class.find(self.parents.first)
|
143
|
+
end
|
144
|
+
|
145
|
+
def parent
|
146
|
+
self.class.find(self.parents.last) rescue nil
|
147
|
+
end
|
148
|
+
|
149
|
+
def ancestors
|
150
|
+
self.class.find(self.parents)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns every node (including the receiver node) from the
|
154
|
+
# tree to the specified block.
|
155
|
+
def each &block
|
156
|
+
yield self
|
157
|
+
children { |child| child.each(&block) }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns the requested node from the set of immediate
|
161
|
+
# children.
|
162
|
+
#
|
163
|
+
# If the key is _numeric_, then the in-sequence array of
|
164
|
+
# children is accessed (see Tree#children).
|
165
|
+
# If the key is not _numeric_, then it is assumed to be
|
166
|
+
# the *name* of the child node to be returned.
|
167
|
+
def [](key)
|
168
|
+
raise "Key needs to be provided" if key == nil
|
169
|
+
|
170
|
+
if key.kind_of?(Integer)
|
171
|
+
children[key]
|
172
|
+
else
|
173
|
+
descendants.select {|child, level| child.name == key}.first[0]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the total number of nodes in this tree, rooted
|
178
|
+
# at the receiver node.
|
179
|
+
def size
|
180
|
+
descendants.size
|
181
|
+
end
|
182
|
+
|
183
|
+
# Convenience synonym for Tree#size
|
184
|
+
def length
|
185
|
+
size()
|
186
|
+
end
|
187
|
+
|
188
|
+
# Pretty prints the tree starting with the receiver node.
|
189
|
+
def print_tree(tab = 0)
|
190
|
+
puts((' ' * tab) + self.to_s)
|
191
|
+
children {|child| child.print_tree(tab + 4)}
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns an array of siblings for this node.
|
195
|
+
# If a block is provided, yeilds each of the sibling
|
196
|
+
# nodes to the block.
|
197
|
+
def siblings
|
198
|
+
if block_given?
|
199
|
+
return nil if is_root?
|
200
|
+
for sibling in parent.children
|
201
|
+
yield sibling if sibling != self
|
202
|
+
end
|
203
|
+
else
|
204
|
+
siblings = []
|
205
|
+
parent.children.each {|sibling| siblings << sibling if sibling != self}
|
206
|
+
siblings
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Provides a comparision operation for the nodes. Comparision
|
211
|
+
# is based on the natural character-set ordering for the
|
212
|
+
# node names.
|
213
|
+
def <=>(other)
|
214
|
+
return +1 if other == nil
|
215
|
+
self.name <=> other.name
|
216
|
+
end
|
217
|
+
|
218
|
+
# Creates a dump representation
|
219
|
+
def create_dump_rep
|
220
|
+
strRep = String.new
|
221
|
+
strRep << @name << @@fieldSep << (is_root? ? @name : parent.name)
|
222
|
+
strRep << @@fieldSep << Marshal.dump(@content) << @@recordSep
|
223
|
+
end
|
224
|
+
|
225
|
+
def _dump(depth)
|
226
|
+
strRep = String.new
|
227
|
+
each {|node| strRep << node.createDumpRep}
|
228
|
+
strRep
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# Private method which sets this node as a root node.
|
233
|
+
def set_as_root!
|
234
|
+
parents = []
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class MongoRecord::Base
|
240
|
+
include MongoTree
|
241
|
+
end
|
data/mongo_tree.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{mongo_tree}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Jim Mulhollnad"]
|
9
|
+
s.date = %q{2009-04-18}
|
10
|
+
s.description = %q{Add hierarchy tree functionality to MongoRecord (MongoDB) models.}
|
11
|
+
s.email = %q{jim@squeejee.com}
|
12
|
+
s.extra_rdoc_files = ["lib/mongo_tree.rb", "README.rdoc"]
|
13
|
+
s.files = ["lib/mongo_tree.rb", "Rakefile", "README.rdoc", "Manifest", "mongo_tree.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/mully/mongo_tree}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Mongo_tree", "--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{mongo_tree}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{Add hierarchy tree functionality to MongoRecord (MongoDB) models.}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
else
|
28
|
+
end
|
29
|
+
else
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mully-mongo_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Mulhollnad
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-18 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Add hierarchy tree functionality to MongoRecord (MongoDB) models.
|
17
|
+
email: jim@squeejee.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- lib/mongo_tree.rb
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- lib/mongo_tree.rb
|
27
|
+
- Rakefile
|
28
|
+
- README.rdoc
|
29
|
+
- Manifest
|
30
|
+
- mongo_tree.gemspec
|
31
|
+
has_rdoc: true
|
32
|
+
homepage: http://github.com/mully/mongo_tree
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options:
|
35
|
+
- --line-numbers
|
36
|
+
- --inline-source
|
37
|
+
- --title
|
38
|
+
- Mongo_tree
|
39
|
+
- --main
|
40
|
+
- README.rdoc
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "1.2"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project: mongo_tree
|
58
|
+
rubygems_version: 1.2.0
|
59
|
+
signing_key:
|
60
|
+
specification_version: 2
|
61
|
+
summary: Add hierarchy tree functionality to MongoRecord (MongoDB) models.
|
62
|
+
test_files: []
|
63
|
+
|