Modifying acts-as-taggable-on tags without touching/saving the tagged record

596 Views Asked by At

While doing some refactoring I ran into a situation where I would like to change the format of the tag names. Initially I tried to just simply migrate by altering the name of the affected ActsAsTaggableOn::Tag records but the issue is that some of the new tag names already exist for different tag contexts.

I could find_each the tagged objects and update them one by one but this is slow and I don't want to touch (updated_at) the records since they didn't really change. Then I thought of renaming the tag unless it exists in which case I would just update the tag_id on the Tagging but then I'm afraid it will mess with the taggings_count or something else.

Is there a way to easily change tags on a record without having to save the record itself? The documentation only really features model extensions and finder methods.

1

There are 1 best solutions below

0
magni- On

Then I thought of renaming the tag unless it exists in which case I would just update the tag_id on the Tagging but then I'm afraid it will mess with the taggings_count or something else.

Disclaimer: we got rid of acts_as_taggable_on years ago, so I haven't had a chance to use it in a while and have not tested the below solution.

With that said, what you mentioned seems like the way to go.

def new_format(name)
  "new_#{name}"
end

tags = %i(tags you want to rename)
model = 'MyModel'
context = :context

ActsAsTaggable::Tag.joins(:taggings).where(name: tags).find_each do |tag|
  if tag.taggings.count == 1
    # this tag is only used in the context we're updating,
    # so we can rename in place
    tag.update(name: new_format(tag.name))
  else
    # this tag is used elsewhere, so we need to create a new tag,
    # update the taggings we care about, and fix the `taggings_count`s
    new_tag = ActsAsTaggable::Tag.create!(name: new_format(tag.name))
    updated = ActsAsTaggable::Tagging.where(
      tag: tag, 
      taggable_type: model, 
      context: context).update_all(tag: new_tag)
    ActsAsTaggableOn::Tag.update_counters(tag.id, taggings_count: -updated)
    ActsAsTaggableOn::Tag.update_counters(new_tag.id, taggings_count: updated)
  end
end

ActiveRecord::Relation.update_all goes straight to SQL and does not touch the related records. ActiveRecord::CounterCache.update_counters also does not touch records by default.