Module: ActiveSorting::Model::ClassMethods

Defined in:
lib/active_sorting/model.rb

Overview

Patches ActiveRecord models

Instance Method Summary collapse

Instance Method Details

#active_sorting_calculate_changes(old_list, new_list, changes = []) ⇒ Object

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



94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/active_sorting/model.rb', line 94

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) ⇒ Object

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



77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/active_sorting/model.rb', line 77

def active_sorting_changes_required(old_list, new_list)
  changes = []
  if old_list.count != new_list.count
    raise ArgumentError, "Sortable new and old lists should be of the same length"
  end
  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_optionsObject

Check provided options



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/active_sorting/model.rb', line 53

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_optionsObject

Default sorting options



44
45
46
47
48
49
50
# File 'lib/active_sorting/model.rb', line 44

def active_sorting_default_options
  {
    order: :asc,
    step: 500,
    scope: []
  }
end

#active_sorting_default_scopeObject



67
68
69
70
71
# File 'lib/active_sorting/model.rb', line 67

def active_sorting_default_scope
  conditions = {}
  conditions[active_sorting_field] = active_sorting_order
  order(conditions)
end

#active_sorting_fieldObject



133
134
135
# File 'lib/active_sorting/model.rb', line 133

def active_sorting_field
  active_sorting_options[:name]
end

#active_sorting_find_by(id_column, value) ⇒ Object



149
150
151
152
153
# File 'lib/active_sorting/model.rb', line 149

def active_sorting_find_by(id_column, value)
  conditions = {}
  conditions[id_column] = value
  find_by(conditions)
end

#active_sorting_make_changes(new_list, changes, id_column) ⇒ Object

Commit changes to database



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/active_sorting/model.rb', line 110

def active_sorting_make_changes(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 = active_sorting_find_by(id_column, new_list[index.next]).active_sorting_value
      n2 = active_sorting_find_by(id_column, new_list[index.next]).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_orderObject



141
142
143
# File 'lib/active_sorting/model.rb', line 141

def active_sorting_order
  active_sorting_options[:order]
end

#active_sorting_scopeObject



145
146
147
# File 'lib/active_sorting/model.rb', line 145

def active_sorting_scope
  active_sorting_options[:scope]
end

#active_sorting_stepObject



137
138
139
# File 'lib/active_sorting/model.rb', line 137

def active_sorting_step
  active_sorting_options[:step]
end

#sort_list(new_list, id_column = :id) ⇒ Object

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

Raises:

  • (ArgumentError)


33
34
35
36
37
38
39
40
41
# File 'lib/active_sorting/model.rb', line 33

def sort_list(new_list, id_column = :id)
  raise ArgumentError, "Sortable list should not be empty" unless new_list.count
  conditions = {}
  conditions[id_column] = new_list
  old_list = unscoped.where(conditions).pluck(id_column)
  raise ArgumentError, "Sortable list should be persisted to database" unless old_list.count
  changes = active_sorting_changes_required(old_list, new_list)
  active_sorting_make_changes(new_list, changes, id_column)
end

#sortable(name, opts = {}) ⇒ Object

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 []



18
19
20
21
22
23
24
25
# 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