brendon/acts_as_list

How to move an item to other scope at specific position

Closed this issue · 3 comments

I'm trying to build a Kanban board with a Board model (acts_as_list scope: :project_id) and a Ticket model (acts_as_list scope: :board_id). Reordering the board or a ticket within a board works fine with ticket.update(position: inputs[:to_position]), but I'm not sure how to move a ticket to a specific position on another board.

I`ve tried:

# Try 1: just update both at the same time
ticket.update(board_id: inputs[:to_board], position: inputs[:to_position])

# Try 2: I already though that this might be a bad idea, so move the ticket to the other board at first and then update the position
ticket.update(board_id: inputs[:to_board])
ticket.update(position: inputs[:to_position])

# Try 3: Ok ok, if I move the ticket with the *current* position to a new board, other items on this board will be updated, but with wrong positions since the new one that I've dragged to could be something different for sure
ticket.update(position: inputs[:to_position])
ticket.update(board_id: inputs[:to_board])

# Give up: doesn't work also, because then the positions on the old board will be updated with wrong values

While try 2 and 3 are definitely wrong approaches, try 1 still makes some sense to me, but then I'll get some duplicate positions sometimes (it isn't even reproducible).

Any hint about how this could be solved would be great 🙏

Hi @gopeter, I think 1 is the correct approach but you may be hitting concurrency issues if requests are interleaved and some are relying on stale data. The gem should detect the scope change and close the gap on the old scope and open a gap at the right position on the new scope but if other requests are updating this data at the same thing you can end up with gaps.

The alternative might be to try the second option but be sure to .reload the ticket before updating its position (on the second line) as the cached position in Rails will be incorrect because the ticket would have been moved to the end of the new scope.

Hope that helps! :D

Hi @brendon, thanks for the fast response! Didn't know that moving the item to another scope moves it to the bottom automatically. That's why I've built this "solution" 😬

ticket.move_to_bottom
ticket.update(position: nil)
ticket.update(board_id: to_board_id)
ticket.update(position: inputs[:to_position])

But I think my problem was that I've fired too many mutations after another (while dragging cards around). Using another approach on the frontend and using try 1 (ticket.update(board_id: inputs[:to_board], position: inputs[:to_position])) works fine now 👍

Thanks @gopeter! Glad you managed to figure it out. That solution sure was convoluted but is essentially what acts_as_list is doing anyway. I think there's probably some work that can be done to improve the transactional isolation of the updates so that proper order is maintained with competing requests. If you ever find the time to dig into it I'd be happy to look at a PR. One of the main problems is stale position data on AR objects because we're using raw SQL to shuffle the positions on other objects.