module Mongoid::Ordering

Add ‘position’ field, multiple methods and multiple callbacks to help with ordering your documents.

Public Instance Methods

at_bottom?() click to toggle source

Is this the lowest sibling?

@example Is this the lowest sibling?

book.at_bottom?

@return [ Boolean ] True if this document is the lowest sibling.

# File lib/mongoid/ordering.rb, line 107
def at_bottom?
  self.lower_siblings.empty?
end
at_top?() click to toggle source

Is this the highest sibling?

@example Is this the highest sibling?

book.at_top?

@return [ Boolean ] True if this document is the highest sibling.

# File lib/mongoid/ordering.rb, line 97
def at_top?
  self.higher_siblings.empty?
end
higher_siblings() click to toggle source

Returns siblings positioned above this document. Siblings with a position lower than this document’s position.

@example Retrieve siblings positioned above this document.

book.higher_siblings

@return [ Mongoid::Criteria ] Criteria to retrieve the document’s higher siblings.

# File lib/mongoid/ordering.rb, line 56
def higher_siblings
  self.siblings.where(:position.lt => self.position)
end
highest_sibling() click to toggle source

Returns the highest sibling (could be self).

@example Retrieve the highest sibling.

book.highest_sibling

@return [ Mongoid::Document ] The highest sibling.

# File lib/mongoid/ordering.rb, line 77
def highest_sibling
  self.siblings_and_self.first
end
lower_siblings() click to toggle source

Returns siblings positioned below this document. Siblings with a position greater than this document’s position.

@example Retrieve siblings positioned below this document.

book.lower_siblings

@return [ Mongoid::Criteria ] Criteria to retrieve the document’s lower siblings.

# File lib/mongoid/ordering.rb, line 67
def lower_siblings
  self.siblings.where(:position.gt => self.position)
end
lowest_sibling() click to toggle source

Returns the lowest sibling (could be self).

@example Retrieve the lowest sibling.

book.lowest_sibling

@return [ Mongoid::Document ] The lowest sibling

# File lib/mongoid/ordering.rb, line 87
def lowest_sibling
  self.siblings_and_self.last
end
move_above(other) click to toggle source

Moves this document above the specified document.

This method changes this document’s scope values if necessary.

@example Move document above another document.

book.move_above(other_book)

@param [ Mongoid::Document ] other The document to Moves this document

above.
# File lib/mongoid/ordering.rb, line 164
def move_above(other)
  return false unless self.sibling_of!(other)

  if self.position > other.position
    new_position = other.position
    other.lower_siblings.and(:position.lt => self.position).each { |s| s.inc(:position, 1) }
    other.inc(:position, 1)
  else
    new_position = other.position - 1
    other.higher_siblings.and(:position.gt => self.position).each { |s| s.inc(:position, -1) }
  end
  self.position = new_position

  self.save!
end
move_below(other) click to toggle source

Moves this document below the specified document.

This method changes this document’s scope values if necessary.

@example Move document below another document.

book.move_below(other_book)

@param [ Mongoid::Document ] other The document to Moves this document

below.
# File lib/mongoid/ordering.rb, line 189
def move_below(other)
  return false unless self.sibling_of!(other)

  if self.position > other.position
    new_position = other.position + 1
    other.lower_siblings.and(:position.lt => self.position).each { |s| s.inc(:position, 1) }
  else
    new_position = other.position
    other.higher_siblings.and(:position.gt => self.position).each { |s| s.inc(:position, -1) }
    other.inc(:position, -1)
  end
  self.position = new_position

  self.save!
end
move_down() click to toggle source

Moves this document one position down.

@example Move document one position down.

book.move_down
# File lib/mongoid/ordering.rb, line 149
def move_down
  return if at_bottom?
  self.siblings.where(position: self.position + 1).first.inc(:position, -1)
  self.inc(:position, 1)
end
move_to_bottom() click to toggle source

Moves this document below all of its siblings.

@example Move document to the bottom.

book.move_to_bottom

@return [ Boolean ] True if the document was moved to the bottom or was

already there.
# File lib/mongoid/ordering.rb, line 130
def move_to_bottom
  return true if at_bottom?
  self.move_below(self.lowest_sibling)
end
move_to_top() click to toggle source

Moves this document above all of its siblings.

@example Move document to the top.

book.move_to_top

@return [ Boolean ] True if the document was moved to the top or was

already there.
# File lib/mongoid/ordering.rb, line 118
def move_to_top
  return true if at_top?
  self.move_above(self.highest_sibling)
end
move_up() click to toggle source

Moves this document one position up.

@example Move document one position up.

book.move_up
# File lib/mongoid/ordering.rb, line 139
def move_up
  return if at_top?
  self.siblings.where(position: self.position - 1).first.inc(:position, 1)
  self.inc(:position, -1)
end

Private Instance Methods

assign_default_position() click to toggle source
# File lib/mongoid/ordering.rb, line 254
def assign_default_position
  return unless self.position.nil? ||
                (self.ordering_scopes.any? { |scope| attribute_changed?(key_for_scope(scope)) } &&
                 !new_record?)

  siblings = self.siblings
  self.position = if siblings.empty? || siblings.map(&:position).compact.empty?
    0
  else
    siblings.max(:position).to_i + 1
  end
end
key_for_scope(scope) click to toggle source
# File lib/mongoid/ordering.rb, line 267
def key_for_scope(scope)
  return nil if scope.nil?
  metadata = self.reflect_on_association(scope)
  metadata ? metadata.key : scope.to_s
end
move_lower_siblings_up() click to toggle source
# File lib/mongoid/ordering.rb, line 207
def move_lower_siblings_up
  return if self.ordering_scopes.any? do |scope|
    relation = send(scope)
    next if relation.nil?

    next unless relation.flagged_for_destroy?

    inverse_metadata = intelligent_inverse_metadata(scope, relation)
    next if inverse_metadata.nil?

    inverse_metadata.destructive?

    # We're testing relation.flagged_for_destroy? && inverse_metadata.destructive?
  end

  self.lower_siblings.each { |s| s.inc(:position, -1) }
end
reposition_former_siblings() click to toggle source
# File lib/mongoid/ordering.rb, line 230
def reposition_former_siblings
  return if self.ordering_scopes.empty?
  
  old_scope_values = {}
  self.ordering_scopes.each do |scope|
    scope_metadata = self.reflect_on_association(scope)
    scope_key = scope_metadata ? scope_metadata.key : scope.to_s
    
    if attribute_changed?(scope_key)
      old_value = attribute_was(scope_key)

      old_scope_values[scope] = if scope_metadata && old_value
        model = scope_metadata.inverse_type ? attribute_was(scope_metadata.inverse_type) : scope_metadata.klass
        scope_metadata.criteria(old_value, model).first
      else
        old_value
      end
    end
  end
  
  former_siblings = self.siblings(scope_values: old_scope_values).where(:position.gt => (attribute_was("position") || 0))
  former_siblings.each { |s| s.inc(:position,  -1) }
end
sibling_reposition_required?() click to toggle source
# File lib/mongoid/ordering.rb, line 225
def sibling_reposition_required?
  return false if self.ordering_scopes.empty?
  self.ordering_scopes.any? { |scope| attribute_changed?(key_for_scope(scope)) } && persisted?
end