ruby-on-railsrubyactiverecordruby-on-rails-5acts-as-list

Rails gem acts_as_list: How to handle reordering of list items when a list item is destroyed?


I'm using acts_as_list v0.9.17 this way:

class ListItem < ActiveRecord::Base
  acts_as_list scope: [:column1_id, :column2_id], :add_new_at => :bottom
end

When a new (scoped) @list_item is created, say the one where column1_id is 1, column2_id is 11 and column3_id is 37, the database looks as follows, as expected:

id  | position | column1_id | column2_id | column3_id
--- | -------- | ---------- | ---------- | ----------
750 | 1        | 1          | 11         | 89
751 | 2        | 1          | 11         | 56
752 | 3        | 1          | 11         | 105
753 | 4        | 1          | 11         | 25
754 | 5        | 1          | 11         | 37

However, when a @list_item is destroyed, say the one where column1_id is 1, column2_id is 11 and column3_id is 56 (record id 751), then the database looks as follows:

id  | position | column1_id | column2_id | column3_id
--- | -------- | ---------- | ---------- | ----------
750 | 1        | 1          | 11         | 89
752 | 3        | 1          | 11         | 105
753 | 4        | 1          | 11         | 25
754 | 5        | 1          | 11         | 37

That means there is a gap for position 2.

How to prevent or adjust the gap? That is, how to handle reordering of list items when a list item is destroyed?


Note: I know there are methods that change position and reorder list but I don't know whether and how to use them to solve the issue (maybe using someway remove_from_list).


Solution

  • As you say you need to use the method you provided, and you can either do it in your controller or the model itself. If you're the kind of guy who likes callbacks, then:

    class ListItem < ActiveRecord::Base
      acts_as_list scope: [:column1_id, :column2_id], :add_new_at => :bottom
      before_destroy { |record| record.remove_from_list }
    
      ....
    end
    

    Some people don't like handling logic like that as a callback, so you can also add it straight into your controller:

    class ListItemsController < Wherever
      ....
      ....
    
      def destroy
        @list_item.remove_from_list
        @list_item.delete
    
        ....
      end
    end
    

    Personally I'd add it into the model but they both do the same thing.

    If you need to try and fix a list that has a missing record in it, say you have LineItems with an ID and position of 0-4 and 6-9, missing position 5. You can make a dirtyish fix in console with:

    LineItem.where('position >= ?', 5).each { |line_item| line_item.update_attributes(position: line_item.position - 1) }
    

    This will find all LineItems with a position over 4, and decrement each of them by one, leaving you with a correctly ordered list from 0-8.