acts-as-taggable-on
acts-as-taggable-on copied to clipboard
dirty methods do not work when tag_list changed using 'add' and 'remove' methods
Loading test environment (Rails 4.0.0)
irb(main):001:0> i = Post.new
=> #
irb(main):002:0> i.tag_list
=> []
irb(main):003:0> i.tag_list_changed?
=> false
irb(main):004:0> i.tag_list.add('sfw')
=> ["sfw"]
irb(main):005:0> i.tag_list_changed?
=> false
irb(main):006:0> i.tag_list = 'sfw'
=> "sfw"
irb(main):007:0> i.tag_list_changed?
=> false
irb(main):008:0> i.tag_list = 'sfw, test'
=> "sfw, test"
irb(main):009:0> i.tag_list_changed?
=> true
I just hit this too.
+1, that's kind of sad since we're only using those methods...
Spent a while to track down this bug in my app. Had to change from u.tag_list.add 'tag'
to u.tag_list = u.tag_list |= ['tag']
as a workaround for this bug.
+1, I've also encountered this issue and would like it to be fixed.
:+1:
Seeing the same issue. Workaround:
# add()
# Prepare the args. Need to bypass extract_and_apply_options! being private...
add_args = ["awesome, slick", {parse: true}]
obj.tag_list.send(:extract_and_apply_options!, add_args)
# Add the lists (creating a new list instance), and invoke the (private) clean! method to deal with duplicates properly
obj.tag_list = (obj.tag_list + add_args).tap {|tl| tl.send(:clean!)
# remove()
# Prepare the args. Again, bypass extract_and_apply_options! being private
remove_args = ["awesome, slick", {parse: true}]
obj.tag_list.send(:extract_and_apply_options!, remove_args)
# Assign the difference
# Note that this replicates the functionality of remove(), which may have some issues with case sensitivity
obj.tag_list = (obj.tag_list - remove_args)
What seems to make these workarounds "work" is that a new TagList object (different object id) gets assigned to the model, whereas add() and remove() modify the TagList contents. add() and remove() don't appear to update the model's @changed_attributes (since from the view of the model, the value "didn't change"). If I'm understanding things correctly, the add() and remove() methods of the TagList would need to somehow "know" what model object they came from and report the changes. Alternatively, the add()/remove() methods need to be moved up into the Taggable implementation
+1 here. I do not really see any reason why this is not of the highest priority. Is the only way to verify if a record's tags are updated.
+1 Bad bug imho. I'll see if I can create a PR.
The add/remove methods should not change the array in place... see https://rails.lighthouseapp.com/projects/8994/tickets/360-dirty-tracking-on-serialized-columns-is-broken
Workaround I've used:
irb(main):001:0> i = Post.new
=> #
irb(main):002:0> i.tag_list
=> []
irb(main):003:0> i.tag_list_changed?
=> false
irb(main):004:0> i.tag_list = ActsAsTaggableOn::TagList.new(i.tag_list).add('sfw')
=> ["sfw"]
irb(main):005:0> i.tag_list_changed?
=> true
Note i.tag_list = ActsAsTaggableOn::TagList.new(i.tag_list).add('sfw')
instead of i.tag_list.add('sfw')
This make your callbacks that rely on tag_list_changed?
to work as expected!
I've moved those into separate module (concern, i.e. put it into app/models/concerns/taggable.rb ):
module Taggable
extend ActiveSupport::Concern
def tag_list_add(tags)
self.tag_list = ActsAsTaggableOn::TagList.new(self.tag_list).add(tags)
end
def tag_list_remove(tags)
self.tag_list = ActsAsTaggableOn::TagList.new(self.tag_list).remove(tags)
end
end
Which is included into model:
class User < ApplicationRecord
include Taggable
...
Which can be used the same way as you use regular tag_list.add:
@user = User.last
@user.tag_list_add('test)
@user.tag_list_changed? # true
@user.save!