module Mongoid::Tree
Mongoid::Tree
¶ ↑
This module extends any Mongoid
document with tree functionality.
Usage¶ ↑
Simply include the module in any Mongoid
document:
class Node include Mongoid::Document include Mongoid::Tree end
Using the tree structure¶ ↑
Each document references many children. You can access them using the #children
method.
node = Node.create node.children.create node.children.count # => 1
Every document references one parent (unless it's a root document).
node = Node.create node.parent # => nil node.children.create node.children.first.parent # => node
Destroying¶ ↑
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.
Available strategies are:
-
:nullify_children – Sets the children's parent_id to null
-
:move_children_to_parent – Moves the children to the current document's parent
-
:destroy_children – Destroys all children by calling their destroy method (invokes callbacks)
-
:delete_descendants – Deletes all descendants using a database query (doesn't invoke callbacks)
Example:
class Node include Mongoid::Document include Mongoid::Tree before_destroy :nullify_children end
Callbacks¶ ↑
Mongoid::Tree
offers callbacks for its rearranging process. This enables you to rebuild certain fields when the document was moved in the tree. Rearranging happens before the document is validated. This gives you a chance to validate your additional changes done in your callbacks. See ActiveModel::Callbacks and ActiveSupport::Callbacks for further details on callbacks.
Example:
class Page include Mongoid::Document include Mongoid::Tree after_rearrange :rebuild_path field :slug field :path private def rebuild_path self.path = self.ancestors_and_self.collect(&:slug).join('/') end end
Public Instance Methods
Is this document an ancestor of the other document?
@param [Mongoid::Tree] other document to check against
@return [Boolean] The document is an ancestor of the other document
# File lib/mongoid/tree.rb, line 307 def ancestor_of?(other) other.parent_ids.include?(self.id) end
Returns a chainable criteria for this document's ancestors
@return [Mongoid::Criteria] Mongoid
criteria to retrieve the documents ancestors
# File lib/mongoid/tree.rb, line 289 def ancestors base_class.where(:_id.in => parent_ids).order(:depth => :asc) end
Returns an array of this document's ancestors and itself
@return [Array<Mongoid::Document>] Array of the document's ancestors and itself
# File lib/mongoid/tree.rb, line 297 def ancestors_and_self ancestors + [self] end
Deletes all descendants using the database (doesn't invoke callbacks)
@return [undefined]
# File lib/mongoid/tree.rb, line 413 def delete_descendants base_class.delete_all(:conditions => { :parent_ids => self.id }) end
Returns the depth of this document (number of ancestors)
@example
Node.root.depth # => 0 Node.root.children.first.depth # => 1
@return [Fixnum] Depth of this document
# File lib/mongoid/tree.rb, line 248 def depth super || parent_ids.count end
Is this document a descendant of the other document?
@param [Mongoid::Tree] other document to check against
@return [Boolean] The document is a descendant of the other document
# File lib/mongoid/tree.rb, line 333 def descendant_of?(other) self.parent_ids.include?(other.id) end
Returns a chainable criteria for this document's descendants
@return [Mongoid::Criteria] Mongoid
criteria to retrieve the document's descendants
# File lib/mongoid/tree.rb, line 315 def descendants base_class.where(:parent_ids => self.id) end
Returns and array of this document and it's descendants
@return [Array<Mongoid::Document>] Array of the document itself and it's descendants
# File lib/mongoid/tree.rb, line 323 def descendants_and_self [self] + descendants end
Destroys all children by calling their destroy method (does invoke callbacks)
@return [undefined]
# File lib/mongoid/tree.rb, line 421 def destroy_children children.destroy_all end
Is this document a leaf node (has no children)?
@return [Boolean] Whether the document is a leaf node
# File lib/mongoid/tree.rb, line 264 def leaf? children.empty? end
Returns all leaves of this document (be careful, currently involves two queries)
@return [Mongoid::Criteria] Mongoid
criteria to retrieve the document's leaves
# File lib/mongoid/tree.rb, line 367 def leaves base_class.where(:_id.nin => base_class.only(:parent_id).collect(&:parent_id)).and(:parent_ids => self.id) end
Moves all children to this document's parent
@return [undefined]
# File lib/mongoid/tree.rb, line 402 def move_children_to_parent children.each do |c| c.parent = self.parent c.save end end
Nullifies all children's parent_id
@return [undefined]
# File lib/mongoid/tree.rb, line 391 def nullify_children children.each do |c| c.parent = c.parent_id = nil c.save end end
Forces rearranging of all children after next save
@return [undefined]
# File lib/mongoid/tree.rb, line 375 def rearrange_children! @rearrange_children = true end
Will the children be rearranged after next save?
@return [Boolean] Whether the children will be rearranged
# File lib/mongoid/tree.rb, line 383 def rearrange_children? !!@rearrange_children end
Returns this document's root node. Returns `self` if the current document is a root node
@example
node = Node.find(...) node.root
@return [Mongoid::Document] The documents root node
# File lib/mongoid/tree.rb, line 277 def root if parent_ids.present? base_class.find(parent_ids.first) else self.root? ? self : self.parent.root end end
Is this document a root node (has no parent)?
@return [Boolean] Whether the document is a root node
# File lib/mongoid/tree.rb, line 256 def root? parent_id.nil? end
Is this document a sibling of the other document?
@param [Mongoid::Tree] other document to check against
@return [Boolean] The document is a sibling of the other document
# File lib/mongoid/tree.rb, line 359 def sibling_of?(other) self.parent_id == other.parent_id end
Returns this document's siblings
@return [Mongoid::Criteria] Mongoid
criteria to retrieve the document's siblings
# File lib/mongoid/tree.rb, line 341 def siblings siblings_and_self.excludes(:id => self.id) end
Returns this document's siblings and itself
@return [Mongoid::Criteria] Mongoid
criteria to retrieve the document's siblings and itself
# File lib/mongoid/tree.rb, line 349 def siblings_and_self base_class.where(:parent_id => self.parent_id) end
Private Instance Methods
# File lib/mongoid/tree.rb, line 450 def position_in_tree errors.add(:parent_id, :invalid) if self.parent_ids.include?(self.id) end
Updates the parent_ids and marks the children for rearrangement when the parent_ids changed
@private @return [undefined]
# File lib/mongoid/tree.rb, line 433 def rearrange if self.parent_id self.parent_ids = parent.parent_ids + [self.parent_id] else self.parent_ids = [] end self.depth = parent_ids.size rearrange_children! if self.parent_ids_changed? end
# File lib/mongoid/tree.rb, line 445 def rearrange_children @rearrange_children = false self.children.each { |c| c.save } end