module ActiveSorting::Model::ClassMethods

Patches ActiveRecord models

Public Instance Methods

active_sorting_calculate_changes(old_list, new_list, changes = []) click to toggle source

Calculate the possible changes required to reorder items in old_list to match new_list order

# File lib/active_sorting/model.rb, line 91
def active_sorting_calculate_changes(old_list, new_list, changes = [])
  new_list.each_with_index do |id, index|
    next unless old_list[index] != id
    # This item has changed
    changes << id
    # Remove it from both lists, rinse and repeat
    new_list.delete(id)
    old_list.delete(id)
    # Recur...
    active_sorting_calculate_changes(old_list, new_list, changes)
    break
  end
  changes
end
active_sorting_changes_required(old_list, new_list) click to toggle source

Calculate the least possible changes required to reorder items in old_list to match new_list order by comparing two proposals from active_sorting_calculate_changes

# File lib/active_sorting/model.rb, line 76
def active_sorting_changes_required(old_list, new_list)
  raise Exceptions::InvalidListSize, "Sortable new and old lists should be of the same length" if old_list.count != new_list.count
  changes = []
  proposal1 = active_sorting_calculate_changes(old_list.dup, new_list.dup)
  if proposal1.count >= (new_list.count / 4)
    proposal2 = active_sorting_calculate_changes(old_list.dup.reverse, new_list.dup.reverse)
    changes = proposal1.count < proposal2.count ? proposal1 : proposal2
  else
    changes = proposal1
  end
  changes
end
active_sorting_check_options() click to toggle source

Check provided options

# File lib/active_sorting/model.rb, line 52
def active_sorting_check_options
  # TODO: columns_hash breaks when database has no tables
  # field_type = columns_hash[active_sorting_field.to_s].type
  # unless field_type == :integer
  #   raise ArgumentError, "Sortable field should be of type Integer, #{field_type} where given"
  # end
  unless active_sorting_step.is_a?(Fixnum)
    raise ArgumentError, "Sortable step should be of type Fixnum, #{active_sorting_step.class.name} where given"
  end
  unless active_sorting_scope.respond_to?(:each)
    raise ArgumentError, "Sortable step should be of type Enumerable, #{active_sorting_scope.class.name} where given"
  end
end
active_sorting_default_options() click to toggle source

Default sorting options

# File lib/active_sorting/model.rb, line 43
def active_sorting_default_options
  {
    order: :asc,
    step: 500,
    scope: []
  }
end
active_sorting_default_scope() click to toggle source
# File lib/active_sorting/model.rb, line 66
def active_sorting_default_scope
  conditions = {}
  conditions[active_sorting_field] = active_sorting_order
  order(conditions)
end
active_sorting_field() click to toggle source
# File lib/active_sorting/model.rb, line 130
def active_sorting_field
  active_sorting_options[:name]
end
active_sorting_find_by(id_column, value) click to toggle source
# File lib/active_sorting/model.rb, line 146
def active_sorting_find_by(id_column, value)
  conditions = {}
  conditions[id_column] = value
  find_by(conditions)
end
active_sorting_make_changes(old_list, new_list, changes, id_column) click to toggle source

Commit changes to database

# File lib/active_sorting/model.rb, line 107
def active_sorting_make_changes(old_list, new_list, changes, id_column)
  new_list.each_with_index do |id, index|
    next unless changes.include?(id)
    if index == new_list.count.pred
      # We're moving an item to last position,
      # increase the count of last item's position
      # by the step
      n1 = active_sorting_find_by(id_column, new_list[index.pred]).active_sorting_value
      n2 = n1 + active_sorting_step
    elsif index == 0
      # We're moving an item to first position
      # Calculate the gap between following 2 items
      n1 = 0
      n2 = active_sorting_find_by(id_column, old_list[index]).active_sorting_value
    else
      # We're moving a non-terminal item
      n1 = active_sorting_find_by(id_column, new_list[index.pred]).active_sorting_value
      n2 = active_sorting_find_by(id_column, new_list[index.next]).active_sorting_value
    end
    active_sorting_find_by(id_column, id).active_sorting_center_item(n1, n2)
  end
end
active_sorting_order() click to toggle source
# File lib/active_sorting/model.rb, line 138
def active_sorting_order
  active_sorting_options[:order]
end
active_sorting_scope() click to toggle source
# File lib/active_sorting/model.rb, line 142
def active_sorting_scope
  active_sorting_options[:scope]
end
active_sorting_step() click to toggle source
# File lib/active_sorting/model.rb, line 134
def active_sorting_step
  active_sorting_options[:step]
end
sort_list(new_list, id_column = :id) click to toggle source

Sorts and updates the database with the given list of items in the given order.

new_list List of ids of records in the desired order id_column the field used for fetching records from the databse,

defaults to :id
# File lib/active_sorting/model.rb, line 33
def sort_list(new_list, id_column = :id)
  raise ArgumentError, "Sortable list should not be empty" if new_list.empty?
  conditions = { id_column => new_list }
  old_list = unscoped.active_sorting_default_scope.where(conditions).pluck(id_column)
  raise Exceptions::RecordsNotFound, "Sortable list should be persisted to database with #{name} Model" if old_list.empty?
  changes = active_sorting_changes_required(old_list, new_list)
  active_sorting_make_changes(old_list, new_list, changes, id_column)
end
sortable(name, opts = {}) click to toggle source

Sets the sortable options

name sortable field name Accepts a Hash of options: order sorting direction, defaults to :asc step stepping value, defaults to 500 scope scope field name, defaults to []

# File lib/active_sorting/model.rb, line 18
def sortable(name, opts = {})
  self.active_sorting_options = active_sorting_default_options.merge(opts)
  active_sorting_options[:name] = name
  active_sorting_check_options
  validates active_sorting_options[:name], presence: true
  default_scope { active_sorting_default_scope }
  before_validation :active_sorting_callback_before_validation
end