mongoid-tree 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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]