awesome_nested_set like methods for scriptaculous and acts_as_list

Junichi Ito edited this page Jan 27, 2014 · 1 revision

For those of you who may have come from awesome_nested_set or one of the equivalents, you would have enjoyed implied ordering, and the ability to place elements anywhere in the tree and also define their order implicitly. Since ancestry only deals with ancestry :) you need to use another plugin or gem to handle the positioning. I opted to use acts_as_list; the gem that was extracted from the rails core a while back. It is a little bit crude, but does the job. In awesome_nested_set and others we have the methods: move_to_(child,left,right)_of. I've recreated these methods below within the context of ancestry and acts_as_list. These methods ensure a clean move from one parent to the other, and also cleanly remove the node from the old list and add it to the new list.

I've also added in a sort method that works well with the awesome_nested_set way of doing things. Originally if you just used acts_as_tree and acts_as_list, scriptactulous would, by default, pass an array of id's and you could just assign each ID it's order in the array + 1. With nested_set we can't do this so we needed to have both the element being moved, plus an array of elements surrounding the element being moved. This way we can work out where the target node needs to be moved to, and use the move_to_(left,right) methods to do so. I like this method better because sometimes not all elements will be presented in the list, so it's better to just deal with what is present.

Add these methods to the model that ancestry is applied to:

# Accepts the typical array of ids from a scriptaculous sortable. It is called on the instance being moved
def sort(array_of_ids)
  if array_of_ids.first == id.to_s
    move_to_left_of siblings.find(array_of_ids.second)
  else
    move_to_right_of siblings.find(array_of_ids[array_of_ids.index(id.to_s) - 1])
  end
end

def move_to_child_of(reference_instance)
  transaction do
    remove_from_list
    self.update_attributes!(:parent => reference_instance)
    add_to_list_bottom
    save!
  end
end

def move_to_left_of(reference_instance)
  transaction do
    remove_from_list
    reference_instance.reload # Things have possibly changed in this list
    self.update_attributes!(:parent_id => reference_instance.parent_id)
    reference_item_position = reference_instance.position
    increment_positions_on_lower_items(reference_item_position)
    self.update_attribute(:position, reference_item_position)
  end
end

def move_to_right_of(reference_instance)
  transaction do
    remove_from_list
    reference_instance.reload # Things have possibly changed in this list
    self.update_attributes!(:parent_id => reference_instance.parent_id)
    if reference_instance.lower_item
      lower_item_position = reference_instance.lower_item.position
      increment_positions_on_lower_items(lower_item_position)
      self.update_attribute(:position, lower_item_position)
    else
      add_to_list_bottom
      save!
    end
  end   
end