acts-as-taggable-on icon indicating copy to clipboard operation
acts-as-taggable-on copied to clipboard

dirty methods do not work when tag_list changed using 'add' and 'remove' methods

Open jlxw opened this issue 11 years ago • 10 comments

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

jlxw avatar Jul 02 '13 01:07 jlxw

I just hit this too.

brandonarbini avatar Aug 13 '14 00:08 brandonarbini

+1, that's kind of sad since we're only using those methods...

vmeyet avatar Jan 29 '15 14:01 vmeyet

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.

himynameisjonas avatar Mar 31 '15 09:03 himynameisjonas

+1, I've also encountered this issue and would like it to be fixed.

austinhappel avatar Oct 04 '15 22:10 austinhappel

:+1:

digerata avatar Oct 20 '15 17:10 digerata

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

Animeye avatar Apr 06 '16 20:04 Animeye

+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.

petrosp avatar Nov 02 '16 16:11 petrosp

+1 Bad bug imho. I'll see if I can create a PR.

vanboom avatar Dec 19 '16 21:12 vanboom

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

vanboom avatar Dec 19 '16 22:12 vanboom

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!

dimitrypo avatar May 29 '20 05:05 dimitrypo