mongoid-tree 0.7.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -2,5 +2,5 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'bson_ext', '>= 1.0.4', :platform => :ruby
6
- gem 'SystemTimer', '>= 1.2.0', :platform => :ruby_18
5
+ gem 'guard-rspec', '>= 0.6.0'
6
+ gem 'ruby_gntp', '>= 0.3.4'
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Benedikt Deicke
1
+ Copyright (c) 2010-2012 Benedikt Deicke
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # mongoid-tree [![Build Status](https://secure.travis-ci.org/benedikt/mongoid-tree.png?branch=master)](http://travis-ci.org/benedikt/mongoid-tree) [![Dependency Status](https://gemnasium.com/benedikt/mongoid-tree.png)](http://gemnasium.com/benedikt/mongoid-tree)
2
+
3
+ A tree structure for Mongoid documents using the materialized path pattern
4
+
5
+ ## Requirements
6
+
7
+ * mongoid (~> 3.0)
8
+
9
+ For a mongoid 2.x compatible version, please use mongoid-tree 0.7.x!
10
+
11
+
12
+ ## Install
13
+
14
+ To install mongoid_tree, simply add it to your Gemfile:
15
+
16
+ gem 'mongoid-tree', :require => 'mongoid/tree'
17
+
18
+ In order to get the latest development version of mongoid-tree:
19
+
20
+ gem 'mongoid-tree', :git => 'git://github.com/benedikt/mongoid-tree', :require => 'mongoid/tree'
21
+
22
+ You might want to remove the `:require => 'mongoid/tree'` option and explicitly `require 'mongoid/tree'` where needed and finally run
23
+
24
+ bundle install
25
+
26
+
27
+ ## Usage
28
+
29
+ Read the API documentation at http://benedikt.github.com/mongoid-tree and take a look at the `Mongoid::Tree` module
30
+
31
+ ```ruby
32
+ class Node
33
+ include Mongoid::Document
34
+ include Mongoid::Tree
35
+ end
36
+ ```
37
+
38
+ ### Utility methods
39
+
40
+ There are several utility methods that help getting to other related documents in the tree:
41
+
42
+ ```ruby
43
+ Node.root
44
+ Node.roots
45
+ Node.leaves
46
+
47
+ node.root
48
+ node.parent
49
+ node.children
50
+ node.ancestors
51
+ node.ancestors_and_self
52
+ node.descendants
53
+ node.descendants_and_self
54
+ node.siblings
55
+ node.siblings_and_self
56
+ node.leaves
57
+ ```
58
+
59
+ In addition it's possible to check certain aspects of the document's position in the tree:
60
+
61
+ ```ruby
62
+ node.root?
63
+ node.leaf?
64
+ node.depth
65
+ node.ancestor_of?(other)
66
+ node.descendant_of?(other)
67
+ node.sibling_of?(other)
68
+ ```
69
+
70
+ See `Mongoid::Tree` for more information on these methods.
71
+
72
+
73
+ ### Ordering
74
+
75
+ `Mongoid::Tree` doesn't order children by default. To enable ordering of tree nodes include the `Mongoid::Tree::Ordering` module. This will add a `position` field to your document and provide additional utility methods:
76
+
77
+ ```ruby
78
+ node.lower_siblings
79
+ node.higher_siblings
80
+ node.first_sibling_in_list
81
+ node.last_sibling_in_list
82
+
83
+ node.move_up
84
+ node.move_down
85
+ node.move_to_top
86
+ node.move_to_bottom
87
+ node.move_above(other)
88
+ node.move_below(other)
89
+
90
+ node.at_top?
91
+ node.at_bottom?
92
+ ```
93
+
94
+ Example:
95
+
96
+ ```ruby
97
+ class Node
98
+ include Mongoid::Document
99
+ include Mongoid::Tree
100
+ include Mongoid::Tree::Ordering
101
+ end
102
+ ```
103
+
104
+ See `Mongoid::Tree::Ordering` for more information on these methods.
105
+
106
+ ### Traversal
107
+
108
+ It's possible to traverse the tree using different traversal methods using the `Mongoid::Tree::Traversal` module.
109
+
110
+ Example:
111
+
112
+ ```ruby
113
+ class Node
114
+ include Mongoid::Document
115
+ include Mongoid::Tree
116
+ include Mongoid::Tree::Traversal
117
+ end
118
+
119
+ node.traverse(:breadth_first) do |n|
120
+ # Do something with Node n
121
+ end
122
+ ```
123
+
124
+ ### Destroying
125
+
126
+ `Mongoid::Tree` does not handle destroying of nodes by default. However it provides several strategies that help you to deal with children of deleted documents. You can simply add them as `before_destroy` callbacks.
127
+
128
+ Available strategies are:
129
+
130
+ * `:nullify_children` -- Sets the children's parent_id to null
131
+ * `:move_children_to_parent` -- Moves the children to the current document's parent
132
+ * `:destroy_children` -- Destroys all children by calling their `#destroy` method (invokes callbacks)
133
+ * `:delete_descendants` -- Deletes all descendants using a database query (doesn't invoke callbacks)
134
+
135
+ Example:
136
+
137
+ ```ruby
138
+ class Node
139
+ include Mongoid::Document
140
+ include Mongoid::Tree
141
+
142
+ before_destroy :nullify_children
143
+ end
144
+ ```
145
+
146
+
147
+ ### Callbacks
148
+
149
+ 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.
150
+
151
+ Example:
152
+
153
+ ```ruby
154
+ class Page
155
+ include Mongoid::Document
156
+ include Mongoid::Tree
157
+
158
+ after_rearrange :rebuild_path
159
+
160
+ field :slug
161
+ field :path
162
+
163
+ private
164
+
165
+ def rebuild_path
166
+ self.path = self.ancestors_and_self.collect(&:slug).join('/')
167
+ end
168
+ end
169
+ ```
170
+
171
+ ### Validations
172
+
173
+ `Mongoid::Tree` currently does not validate the document's children or parent associations by default. To explicitly enable validation for children and parent documents it's required to add a `validates_associated` validation.
174
+
175
+ Example:
176
+
177
+ ```ruby
178
+ class Node
179
+ include Mongoid::Document
180
+ include Mongoid::Tree
181
+
182
+ validates_associated :parent, :children
183
+ end
184
+ ```
185
+
186
+ ## Build Status
187
+
188
+ mongoid-tree is on [Travis CI](http://travis-ci.org/benedikt/mongoid-tree) running the specs on Ruby Head, Ruby 1.9.3, JRuby (1.9 mode), and Rubinius (1.9 mode).
189
+
190
+ ## Known issues
191
+
192
+ See [https://github.com/benedikt/mongoid-tree/issues](https://github.com/benedikt/mongoid-tree/issues)
193
+
194
+
195
+ ## Repository
196
+
197
+ See [https://github.com/benedikt/mongoid-tree](https://github.com/benedikt/mongoid-tree) and feel free to fork it!
198
+
199
+
200
+ ## Contributors
201
+
202
+ See a list of all contributors at [https://github.com/benedikt/mongoid-tree/contributors](https://github.com/benedikt/mongoid-tree/contributors). Thanks a lot everyone!
203
+
204
+
205
+ ## Support
206
+
207
+ If you like mongoid-tree and want to support the development, I would appreciate a small donation:
208
+
209
+ [![Pledgie](http://www.pledgie.com/campaigns/12137.png?skin_name=chrome)](http://www.pledgie.com/campaigns/12137)
210
+
211
+ [![Flattr](https://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=benediktdeicke&url=https://github.com/benedikt/mongoid-tree&title=mongoid-tree&language=&tags=github&category=software)
212
+
213
+ ## Copyright
214
+
215
+ Copyright (c) 2010-2012 Benedikt Deicke. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'rspec/core/rake_task'
2
- require 'rdoc/task'
2
+ require 'yard'
3
3
 
4
4
  spec = Gem::Specification.load("mongoid-tree.gemspec")
5
5
 
@@ -7,13 +7,7 @@ RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task :default => :spec
9
9
 
10
- RDoc::Task.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
10
+ YARD::Rake::YardocTask.new(:doc)
17
11
 
18
12
  desc "Build the .gem file"
19
13
  task :build do
data/lib/mongoid/tree.rb CHANGED
@@ -1,4 +1,4 @@
1
- module Mongoid # :nodoc:
1
+ module Mongoid
2
2
  ##
3
3
  # = Mongoid::Tree
4
4
  #
@@ -83,12 +83,12 @@ module Mongoid # :nodoc:
83
83
  autoload :Traversal, 'mongoid/tree/traversal'
84
84
 
85
85
  included do
86
- references_many :children, :class_name => self.name, :foreign_key => :parent_id, :inverse_of => :parent, :autosave => true, :validate => false
86
+ has_many :children, :class_name => self.name, :foreign_key => :parent_id, :inverse_of => :parent, :validate => false
87
87
 
88
- referenced_in :parent, :class_name => self.name, :inverse_of => :children, :index => true, :validate => false
88
+ belongs_to :parent, :class_name => self.name, :inverse_of => :children, :index => true, :validate => false
89
89
 
90
90
  field :parent_ids, :type => Array, :default => []
91
- index :parent_ids
91
+ index :parent_ids => 1
92
92
 
93
93
  set_callback :save, :after, :rearrange_children, :if => :rearrange_children?
94
94
  set_callback :validation, :before do
@@ -103,29 +103,39 @@ module Mongoid # :nodoc:
103
103
  end
104
104
 
105
105
  ##
106
- # :singleton-method: root
107
- # Returns the first root document
108
-
109
- ##
110
- # :singleton-method: roots
111
- # Returns all root documents
112
-
113
- ##
114
- # :singleton-method: leaves
115
- # Returns all leaves (be careful, currently involves two queries)
116
-
117
- ##
118
- # This module includes those methods documented above
119
- module ClassMethods # :nodoc:
120
-
106
+ # This module implements class methods that will be available
107
+ # on the document that includes Mongoid::Tree
108
+ module ClassMethods
109
+
110
+ ##
111
+ # Returns the first root document
112
+ #
113
+ # @example
114
+ # Node.root
115
+ #
116
+ # @return [Mongoid::Document] The first root document
121
117
  def root
122
- first(:conditions => { :parent_id => nil })
118
+ roots.first
123
119
  end
124
120
 
121
+ ##
122
+ # Returns all root documents
123
+ #
124
+ # @example
125
+ # Node.roots
126
+ #
127
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve all root documents
125
128
  def roots
126
129
  where(:parent_id => nil)
127
130
  end
128
131
 
132
+ ##
133
+ # Returns all leaves (be careful, currently involves two queries)
134
+ #
135
+ # @example
136
+ # Node.leaves
137
+ #
138
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve all leave nodes
129
139
  def leaves
130
140
  where(:_id.nin => only(:parent_id).collect(&:parent_id))
131
141
  end
@@ -133,140 +143,236 @@ module Mongoid # :nodoc:
133
143
  end
134
144
 
135
145
  ##
136
- # :singleton-method: before_rearrange
137
- # Sets a callback that is called before the document is rearranged
138
- # (Generated by ActiveSupport)
146
+ # @!method before_rearrange
147
+ # @!scope class
148
+ #
149
+ # Sets a callback that is called before the document is rearranged
150
+ #
151
+ # @example
152
+ # class Node
153
+ # include Mongoid::Document
154
+ # include Mongoid::Tree
155
+ #
156
+ # before_rearrage :do_something
157
+ #
158
+ # private
159
+ #
160
+ # def do_something
161
+ # # ...
162
+ # end
163
+ # end
164
+ #
165
+ # @note Generated by ActiveSupport
166
+ #
167
+ # @return [undefined]
139
168
 
140
169
  ##
141
- # :singleton-method: after_rearrange
142
- # Sets a callback that is called after the document is rearranged
143
- # (Generated by ActiveSupport)
170
+ # @!method after_rearrange
171
+ # @!scope class
172
+ #
173
+ # Sets a callback that is called after the document is rearranged
174
+ #
175
+ # @example
176
+ # class Node
177
+ # include Mongoid::Document
178
+ # include Mongoid::Tree
179
+ #
180
+ # after_rearrange :do_something
181
+ #
182
+ # private
183
+ #
184
+ # def do_something
185
+ # # ...
186
+ # end
187
+ # end
188
+ #
189
+ # @note Generated by ActiveSupport
190
+ #
191
+ # @return [undefined]
144
192
 
145
193
  ##
146
- # :method: children
147
- # Returns a list of the document's children. It's a <tt>references_many</tt> association.
148
- # (Generated by Mongoid)
194
+ # @!method children
195
+ # Returns a list of the document's children. It's a <tt>references_many</tt> association.
196
+ #
197
+ # @note Generated by Mongoid
198
+ #
199
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the document's children
149
200
 
150
201
  ##
151
- # :method: parent
152
- # Returns the document's parent (unless it's a root document). It's a <tt>referenced_in</tt> association.
153
- # (Generated by Mongoid)
202
+ # @!method parent
203
+ # Returns the document's parent (unless it's a root document). It's a <tt>referenced_in</tt> association.
204
+ #
205
+ # @note Generated by Mongoid
206
+ #
207
+ # @return [Mongoid::Document] The document's parent document
154
208
 
155
209
  ##
156
- # :method: parent=
157
- #call-seq:
158
- # parent= document
210
+ # @!method parent=(document)
211
+ # Sets this documents parent document.
159
212
  #
160
- # Sets this documents parent document.
161
- # (Generated by Mongoid)
162
-
213
+ # @note Generated by Mongoid
214
+ #
215
+ # @param [Mongoid::Tree] document
216
+
163
217
  ##
164
- # :method: parent_ids
165
- # Returns a list of the document's parent_ids, starting with the root node.
166
- # (Generated by Mongoid)
218
+ # @!method parent_ids
219
+ # Returns a list of the document's parent_ids, starting with the root node.
220
+ #
221
+ # @note Generated by Mongoid
222
+ #
223
+ # @return [Array<BSON::ObjectId>] The ids of the document's ancestors
167
224
 
168
225
  ##
169
226
  # Is this document a root node (has no parent)?
227
+ #
228
+ # @return [Boolean] Whether the document is a root node
170
229
  def root?
171
230
  parent_id.nil?
172
231
  end
173
232
 
174
233
  ##
175
234
  # Is this document a leaf node (has no children)?
235
+ #
236
+ # @return [Boolean] Whether the document is a leaf node
176
237
  def leaf?
177
238
  children.empty?
178
239
  end
179
240
 
180
241
  ##
181
242
  # Returns the depth of this document (number of ancestors)
243
+ #
244
+ # @example
245
+ # Node.root.depth # => 0
246
+ # Node.root.children.first.depth # => 1
247
+ #
248
+ # @return [Fixnum] Depth of this document
182
249
  def depth
183
250
  parent_ids.count
184
251
  end
185
252
 
186
253
  ##
187
- # Returns this document's root node
254
+ # Returns this document's root node. Returns `self` if the
255
+ # current document is a root node
256
+ #
257
+ # @example
258
+ # node = Node.find(...)
259
+ # node.root
260
+ #
261
+ # @return [Mongoid::Document] The documents root node
188
262
  def root
189
263
  if parent_ids.present?
190
- return base_class.find(parent_ids.first)
264
+ base_class.find(parent_ids.first)
191
265
  else
192
- return self.root? ? self : self.parent.root
266
+ self.root? ? self : self.parent.root
193
267
  end
194
268
  end
195
269
 
196
270
  ##
197
271
  # Returns a chainable criteria for this document's ancestors
272
+ #
273
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the documents ancestors
198
274
  def ancestors
199
275
  base_class.where(:_id.in => parent_ids)
200
276
  end
201
277
 
202
278
  ##
203
279
  # Returns an array of this document's ancestors and itself
280
+ #
281
+ # @return [Array<Mongoid::Document>] Array of the document's ancestors and itself
204
282
  def ancestors_and_self
205
283
  ancestors + [self]
206
284
  end
207
285
 
208
286
  ##
209
287
  # Is this document an ancestor of the other document?
288
+ #
289
+ # @param [Mongoid::Tree] other document to check against
290
+ #
291
+ # @return [Boolean] The document is an ancestor of the other document
210
292
  def ancestor_of?(other)
211
293
  other.parent_ids.include?(self.id)
212
294
  end
213
295
 
214
296
  ##
215
297
  # Returns a chainable criteria for this document's descendants
298
+ #
299
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the document's descendants
216
300
  def descendants
217
301
  base_class.where(:parent_ids => self.id)
218
302
  end
219
303
 
220
304
  ##
221
- # Returns and array of this document's descendants and itself
305
+ # Returns and array of this document and it's descendants
306
+ #
307
+ # @return [Array<Mongoid::Document>] Array of the document itself and it's descendants
222
308
  def descendants_and_self
223
309
  [self] + descendants
224
310
  end
225
311
 
226
312
  ##
227
313
  # Is this document a descendant of the other document?
314
+ #
315
+ # @param [Mongoid::Tree] other document to check against
316
+ #
317
+ # @return [Boolean] The document is a descendant of the other document
228
318
  def descendant_of?(other)
229
319
  self.parent_ids.include?(other.id)
230
320
  end
231
321
 
232
322
  ##
233
323
  # Returns this document's siblings
324
+ #
325
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the document's siblings
234
326
  def siblings
235
327
  siblings_and_self.excludes(:id => self.id)
236
328
  end
237
329
 
238
330
  ##
239
331
  # Returns this document's siblings and itself
332
+ #
333
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the document's siblings and itself
240
334
  def siblings_and_self
241
335
  base_class.where(:parent_id => self.parent_id)
242
336
  end
243
337
 
244
338
  ##
245
339
  # Is this document a sibling of the other document?
340
+ #
341
+ # @param [Mongoid::Tree] other document to check against
342
+ #
343
+ # @return [Boolean] The document is a sibling of the other document
246
344
  def sibling_of?(other)
247
345
  self.parent_id == other.parent_id
248
346
  end
249
347
 
250
348
  ##
251
349
  # Returns all leaves of this document (be careful, currently involves two queries)
350
+ #
351
+ # @return [Mongoid::Criteria] Mongoid criteria to retrieve the document's leaves
252
352
  def leaves
253
353
  base_class.where(:_id.nin => base_class.only(:parent_id).collect(&:parent_id)).and(:parent_ids => self.id)
254
354
  end
255
355
 
256
356
  ##
257
357
  # Forces rearranging of all children after next save
358
+ #
359
+ # @return [undefined]
258
360
  def rearrange_children!
259
361
  @rearrange_children = true
260
362
  end
261
363
 
262
364
  ##
263
365
  # Will the children be rearranged after next save?
366
+ #
367
+ # @return [Boolean] Whether the children will be rearranged
264
368
  def rearrange_children?
265
369
  !!@rearrange_children
266
370
  end
267
371
 
268
372
  ##
269
373
  # Nullifies all children's parent_id
374
+ #
375
+ # @return [undefined]
270
376
  def nullify_children
271
377
  children.each do |c|
272
378
  c.parent = c.parent_id = nil
@@ -276,6 +382,8 @@ module Mongoid # :nodoc:
276
382
 
277
383
  ##
278
384
  # Moves all children to this document's parent
385
+ #
386
+ # @return [undefined]
279
387
  def move_children_to_parent
280
388
  children.each do |c|
281
389
  c.parent_id = self.parent_id
@@ -285,17 +393,28 @@ module Mongoid # :nodoc:
285
393
 
286
394
  ##
287
395
  # Deletes all descendants using the database (doesn't invoke callbacks)
396
+ #
397
+ # @return [undefined]
288
398
  def delete_descendants
289
399
  base_class.delete_all(:conditions => { :parent_ids => self.id })
290
400
  end
291
401
 
292
402
  ##
293
403
  # Destroys all children by calling their #destroy method (does invoke callbacks)
404
+ #
405
+ # @return [undefined]
294
406
  def destroy_children
295
407
  children.destroy_all
296
408
  end
297
409
 
298
410
  private
411
+
412
+ ##
413
+ # Updates the parent_ids and marks the children for
414
+ # rearrangement when the parent_ids changed
415
+ #
416
+ # @private
417
+ # @return [undefined]
299
418
  def rearrange
300
419
  if self.parent_id
301
420
  self.parent_ids = parent.parent_ids + [self.parent_id]