module Lexoranking::Model

Allows your models to sort elements using lexographical sorting

Options: self.ranking_scope - Determine if the elements should be scoped to a specific association before sorting.

Public Instance Methods

rank_first() click to toggle source

Ranks the record to the first postiion of the list and saves it.

# File lib/lexoranking/model.rb, line 32
def rank_first
  rank_to(0)
end
rank_last() click to toggle source

Ranks the record to the last position of the list and saves it.

# File lib/lexoranking/model.rb, line 27
def rank_last
  rank_to(:last)
end
rank_to(position) click to toggle source

Ranks the record to a specific position in the list and saves it.

# File lib/lexoranking/model.rb, line 37
def rank_to(position)
  model_collection = get_collection
  # This return statement handle the case of when there is only one
  # element in the scoped collection and we call this method. Since
  # there is only one element in the collection and the rank column is
  # present there is no reason to calculate the ranking value again.
  return if model_collection.size == 1 && rank.present?
  position = position == :last ? model_collection.size-1 : position.to_i
  previous, nextt = get_prev_next_elements(position, model_collection)
  ranking = calculate_ranking(previous, nextt)

  send("#{self.class.ranking_column}=", ranking)
  save
end
save() click to toggle source

Rank the record to the last position of the list before saving it.

Calls superclass method
# File lib/lexoranking/model.rb, line 21
def save
  rank_to(:last) if rank.nil?
  super
end

Private Instance Methods

_raise_ranking_scope_not_valid() click to toggle source
# File lib/lexoranking/model.rb, line 111
def _raise_ranking_scope_not_valid
  raise ::Lexoranking::RankingScopeNotValid.new(
    "The #{ranking_model_scope} column does not exists in this model #{self.class.name}",
    self
  )
end
calculate_ranking(prev, nextt) click to toggle source

Calculated the ranking value for the current record(self) based on the previous and nextt elements in the list.

# File lib/lexoranking/model.rb, line 99
def calculate_ranking(prev, nextt)
  Lexoranking::Main.perform(
    prev&.send(self.class.ranking_column),
    nextt&.send(self.class.ranking_column)
  )
end
collection_by_ranking_scope() click to toggle source

Returns the model collection scoped to the ranking scope column in the model

If the ranking scope colunm does not exists in the model or it is not an association it will raise an exception error.

raise {Lexoranking::RankingScopeNotValid}

# File lib/lexoranking/model.rb, line 60
def collection_by_ranking_scope
  model_scope = ranking_model_scope
  validate_ranking_scope_column!(model_scope) || _raise_ranking_scope_not_valid

  self.class.ranked.where("#{model_scope}": send(model_scope))
end
get_collection() click to toggle source

Returns a ranked model collection of elements if the ranking model scope is not present otherwise it will return a models collection scoped to the ranking scope column.

# File lib/lexoranking/model.rb, line 69
def get_collection
  @get_collection ||= ranking_model_scope.present? ? collection_by_ranking_scope : self.class.ranked
end
get_prev_next_elements(position, collection) click to toggle source

Returns the previous and next elements where the new element will be position inside the list.

If the position we want to sort the element is position zero, it will return nil for the previous element and the the first element of the list.

If the position is less than the index of the current element(self) that means that we try to move the element from a higher position to a lower position in the list and in this case we decrement the position by 1 so that we keep the zero base logic, otherwise we are trying to move the element from a lower position of the list to a higher position and in this case we keep the position value as it is. We use the position to offset those number of elements in the list and then take the next two. That way we will have the two element where we want to position the record in between.

# File lib/lexoranking/model.rb, line 90
def get_prev_next_elements(position, collection)
  return [collection[0], nil] if position == 0 && collection.size == 1
  return [nil, collection[0]] if position <= 0
  position -= 1 if (collection.map(&:id).index(id) || 0) > position
  collection.offset(position).limit(2)
end
ranking_model_scope() click to toggle source

Returns the ranking scope column

# File lib/lexoranking/model.rb, line 74
def ranking_model_scope
  @ranking_model_scope ||= self.class.ranking_scope
end
validate_ranking_scope_column!(model_scope) click to toggle source

Validates that the ranking scope column exists in the model or is an association for the model.

# File lib/lexoranking/model.rb, line 107
def validate_ranking_scope_column!(model_scope)
  self.class.columns.map(&:name).include?(model_scope.to_s) || self.class.reflect_on_association(model_scope)
end