Should acts_as_list automatically reorder items when a resource is updated with update_attributes?

3.9k views Asked by At

I'm using the acts_as_list plugin with rails 3.1 and whenever I add or remove a resource existing positions are ordered and updated fine. However, when updating the position of a resource with update_attributes no reordering seems to take place and I can be left with resources that have duplicated positions. Is this the correct behaviour of acts_as_list?

I've searched for clarification but documentation on this plug in is really limited.

I'm not using javascript at this stage, the position is simply determined with a select box that has an appropriately limited range.

Thanks

2

There are 2 answers

0
bricker On BEST ANSWER

There's nothing in the plugin that gets called when the object is updated. You'll have to do it manually using the built-in methods that acts_as_list provides.

The ruby file for this plugin is only 300 lines and easy to follow, it will tell you everything there is to know about acts_as_list... honestly there's not much to it: https://github.com/swanandp/acts_as_list/blob/master/lib/acts_as_list/active_record/acts/list.rb

To do it manually, just get all items with a position higher than the current item and increment each one. This is how I handle it in my apps (not using acts_as_list but the concept is there). This is example is for ordering forums:

forum.rb

PositionOptions = [["First Forum", "-1"]] + all.map{|forum| ["After #{forum.subject}", forum.position]}

attr_accessor :position_before
  before_save :set_position, :if => "position_before.present?"
    def set_position
      self.position = position_before.to_i + 1
    end

  after_save :do_positions, :if => "position_before.present?"
    def do_positions
      Forum.where('position >= ? AND id != ?', position, id).order('position, updated_at DESC').all.each_with_index do |forum, index|
        forum.update_attribute('position', position + index + 1)
      end
    end

view

<% position_options = Forum::PositionOptions %>
<%= f.select :position_before, position_options.reject {|forum| @forum.position == forum[1]}, :selected => position_options[position_options.index {|p| p[1] == @forum.position} - 1][1] %>

Maybe not the best way to do it, but it was the best I could come up with. It will probably be too expensive if you plan on the positions being updated often, or if there will be a lot of objects being positioned... but it works for small collections like forums.

0
robd On

I handled this by overriding the writer for the has_many attribute to clear the list first eg in this case where Photo acts_as_list, I overrode the writer in the referring class:

def photos_with_clear=(photos)
  self.photos_without_clear=[]
  self.photos_without_clear=photos
end
alias_method_chain :photos=, :clear

This is obviously quite inefficient because the photos are cleared and recreated whenever the photos collection is set, but it has the advantage of working with both update_attributes(:photos=>[<Photo 1>, <Photo 2>]) or update_attributes(:photo_ids=>[123, 456]) (at least in rails 2.3.18).