mbleigh / acts-as-taggable-on

A tagging plugin for Rails applications that allows for custom tagging along dynamic contexts.
http://mbleigh.lighthouseapp.com/projects/10116-acts-as-taggable-on
MIT License
4.96k stars 1.2k forks source link

Question: How to avoid ActsAsTaggableOn::Tag Load on record updates when no tags changed #975

Closed dpaluy closed 4 years ago

dpaluy commented 4 years ago

My class has several acts_as_taggable_on. Each update to the record, triggers ActsAsTaggableOn::Tag Load query after update

Example:

class Operator < ApplicationRecord
  acts_as_taggable_on :tour_types
  acts_as_taggable_on :regions
  acts_as_taggable_on :activities
end
Operator Update (0.5ms)  UPDATE "operators" SET "updated_at" = $1, "highlights" = $2 WHERE "operators"."id" = $3  [["updated_at", "2019-11-12 09:29:08.993683"], ["highlights", "{First,Second}"], ["id", 1]]
  ActsAsTaggableOn::Tag Load (0.4ms)  SELECT "tags".* FROM "tags" WHERE (LOWER(name) = LOWER('africa') OR LOWER(name) = LOWER('amaerica') OR LOWER(name) = LOWER('alaska'))
  ActsAsTaggableOn::Tag Load (0.7ms)  SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'regions' AND taggings.tagger_id IS NULL)  [["taggable_id", 1], ["taggable_type", "Operator"]]
  ActsAsTaggableOn::Tag Load (0.4ms)  SELECT "tags".* FROM "tags" WHERE (LOWER(name) = LOWER('africa') OR LOWER(name) = LOWER('amaerica') OR LOWER(name) = LOWER('alaska'))
  ActsAsTaggableOn::Tag Load (0.7ms)  SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'regions' AND taggings.tagger_id IS NULL)  [["taggable_id", 1], ["taggable_type", "Operator"]]

How to avoid those Tag load queries?

ghost commented 4 years ago

Looks like it is impossible without patching acts_as_taggable_on/taggable/core.rb:

require_relative 'tagged_with_query'

module ActsAsTaggableOn::Taggable
  module Core

    def self.included(base)
      base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods

      base.class_eval do
        attr_writer :custom_contexts
        after_save :save_tags
      end

...

    def save_tags
      tagging_contexts.each do |context|
        next unless tag_list_cache_set_on(context)
        # List of currently assigned tag names
        tag_list = tag_list_cache_on(context).uniq

        # Find existing tags or create non-existing tags:
        tags = find_or_create_tags_from_list_with_context(tag_list, context)
...

lib/acts_as_taggable_on/taggable/ownership.rb:

module ActsAsTaggableOn::Taggable
  module Ownership
    def self.included(base)
      base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods

      base.class_eval do
        after_save :save_owned_tags
      end
...

    def save_owned_tags
      tagging_contexts.each do |context|
        cached_owned_tag_list_on(context).each do |owner, tag_list|
...

save_tags and save_owned_tags always gets triggered to load tags unnecessarily.