RailsCasts Pro episodes are now free!

Learn more or hide this

Richard Nichol's Profile

GitHub User: richardonichol

Comments by Richard Nichol

Avatar

Took too long to edit to correct my mistake. The code in the form above only works when the moved item is moved up. This is the hastily corrected version which is in need of some refactoring:

ruby
  def sort
    ids = params[:faq].map { |id| id.to_i }
    originals = original_ids(ids)
    moved = moved_id(ids, originals)
    item = Faqs.find(moved)
    item.insert_at(moved_to(ids,item)) # Using acts_as_list
  end

  protected

  def moved_id ids, originals
    offset = first_changed_index(ids)
    new_candidate = ids[offset]
    old_candidate = originals[offset]
    new_delta = (ids.index(new_candidate) - originals.index(new_candidate)).abs
    old_delta = (ids.index(old_candidate) - originals.index(old_candidate)).abs
    new_delta > old_delta ? new_candidate : old_candidate
  end

  def first_changed_index ids
    ids.each_with_index do |id, i|
      return i if original_ids[i] != id
    end
  end

  def moved_to ids, item
    ids.index(item.id) + 1
  end

  def original_ids ids
    Faq.where(id: ids).order(:position).pluck(:id)
  end
Avatar

It is actually possible to do all updates in a single query, preferably using the fact that your model is using acts_as_list:

ruby
def sort
  ids = params[:faq].map { |id| id.to_i }
  item = first_moved_item(ids)
  item.insert_at(moved_to(ids,item)) # Using acts_as_list
end

def first_moved_item ids
  ids.each_with_index do |id, i|
    return Faq.find(originals[i]) if originals[i] != id
  end
end

def moved_to ids, item
  ids.index(item.id) + 1
end

def originals
  @originals ||= Faq.where(id: params[:faq]).order(:position).pluck(:id)
end

This makes use of the fact that the insert_at method already has the logic to optimally execute the move in the most efficient fashion. For me this is more than an order of magnitude faster since I was using sorted on a list that could contain upwards of 500 items.