acts_as_list_no_update does not work with nested blocks.
conorbdaly opened this issue · 5 comments
I encountered this issue when trying to copy a parent - child structure while still preserving positioning.
The code would have been similar to:
::Parents.acts_as_list_no_update do
Parents.all.each do |parent|
copy = parent.dup
copy.save!
::Children.acts_as_list_no_update do
parent.children.each do |child|
child_copy = child.dup
copy.children << child_copy
child_copy.save!
end
end
end
end
The problem is that acts_as_list_no_update is automatically disabled for all classes at the end of each block.
So after all the children have been copied for a parent, and the end of the ::Children.acts_as_list_no_update block, the no_update for the parent is disabled, meaning for each subsequent parent save acts_as_list alters the positions.
(Note that the above snippet is only for illustration, in my situation the parents are in seperate scoped lists).
I believe this issue stems from
https://github.com/swanandp/acts_as_list/blob/248bf23161de8c0fbc1ca05b3b7f8ba59a12f79b/lib/acts_as_list/active_record/acts/no_update.rb#L95
This line clears every class that no_update is enabled for.
Instead of:
extracted_klasses.clear
this line should probably read
extracted_klasses.reject!{|klass| klasses.include?(klass)| }
Hi @conorbdaly, thanks for bring this up. There's a couple of approaches you could take.
- I have similar copying functionality in my application and I just let acts_as_list assign new positions for the duplicated items as they're made. As long as you are fetching them in order (I assume you enhance your
has_many
relationships with anorder
on the position column?has_many :children, -> { order(:position) }
) the duplicated items should be added in order also. - You can use the extra classes functionality of
acts_as_list_no_update
:
https://github.com/swanandp/acts_as_list/blob/248bf23161de8c0fbc1ca05b3b7f8ba59a12f79b/lib/acts_as_list/active_record/acts/no_update.rb#L73 You can specify also pausing updates for the Child class like:Parent.acts_as_list_no_update([Child])
Give those a go and let me know how you get on :)
Hi @brendon, thanks for your response.
Yes, we currently have a workaround, by only explicitly wrapping each .save! call instead of wrapping each full loop.
I think that my point is more that its unexpected behavior to close the outer no_update block when an inner block ends. By using the block syntax, I would expect all acts_as_list operations for that class to be disabled until the end of the block, regardless of what other operations happen within the block.
We actually got hit with this as a bug in production, and it can be pretty hard to spot. Most DBMS's will give the results back based on actual DB id, so if the list was never re-ordered, the results will be correct.
Now, I know that one was our fault for not testing properly, but the end result was intermittent and difficult to track down.
I do understand if you guys don't want to change the functionality, but would you consider maybe adding a line or two to the readme.md to explain that nested blocks wont work with no_update?
Thanks @conorbdaly, I think you're right here in that the documentation is kind of hidden away as to how to correctly use this feature: https://github.com/swanandp/acts_as_list/blob/248bf23161de8c0fbc1ca05b3b7f8ba59a12f79b/lib/acts_as_list/active_record/acts/no_update.rb
Perhaps we could forbid nested no_update
blocks and warn people to use the additional class method instead, or we could change this so that one could nest no_update
blocks.
Would you be keen to look into this?
Threw an hour into this and came up with #343. This should enable nesting of no_update blocks.
Thanks @conorbdaly, I really appreciate the time you put into this. I've merged this and will do a new release shortly.