Module: EasyTags::TaggableMethods

Defined in:
lib/easy_tags/taggable_methods.rb

Overview

Taggable instance methods

Class Method Summary collapse

Class Method Details

.inject(class_instance:) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/MethodLength


6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/easy_tags/taggable_methods.rb', line 6

def inject(class_instance:)
  # rubocop:disable Metrics/BlockLength
  class_instance.class_eval do
    has_many(
      :taggings,
      as: :taggable,
      dependent: :destroy,
      class_name: '::EasyTags::Tagging',
      inverse_of: :taggable
    )

    has_many(
      :base_tags,
      through: :taggings,
      source: :tag,
      class_name: '::EasyTags::Tag',
      inverse_of: :tag
    )

    after_save :_update_taggings, :_refresh_tagging
    after_find :_refresh_tagging

    # override ActiveRecord::Persistence#reload
    # to refresh tags each time the model instance gets reloaded
    def reload
      _refresh_tagging
      super
    end

    private

      attr_accessor :_taggable_contexts

      def _taggable_contexts
        @_taggable_contexts ||= {}
      end

      def _update_taggings
        tagging_contexts.each do |context|
          context_tags = _taggable_context(context)

          next unless context_tags.changed?

          context_tags.new_tags.each do |tag_name|
            tag = Tag.find_or_create_by!(name: tag_name)
            taggings.create!(context: context, tag: tag)
          end

          taggings
            .joins(:tag)
            .where(context: context)
            .where(Tag.table_name => { name: context_tags.removed_tags })
            .destroy_all
        end
      end

      def _refresh_tagging
        tagging_contexts.each do |context|
          _taggable_context(context).refresh
        end
      end

      def _mark_dirty(context:, taggable_context:)
        write_attribute("#{context}_list", taggable_context.tags.to_s)
        set_attribute_was("#{context}_list", taggable_context.persisted_tags.to_s)
        attribute_will_change!("#{context}_list")
      end

      def _taggable_context(context)
        _taggable_contexts[context] ||= TaggableContext.new(
          context: context,
          refresh_persisted_tags: lambda {
            taggings.joins(:tag).where(context: context).pluck(:name)
          },
          on_change: lambda { |tag_context|
            _mark_dirty(context: context, taggable_context: tag_context)
          }
        )
      end

      def _notify_tag_change(type:, tagging:)
        callbacks_for_type = tagging_callbacks[tagging.context.to_sym].select do |callback|
          callback.type == type
        end

        callbacks_for_type.each do |callback|
          callback.run(taggable: self, tagging: tagging)
        end
      end

      def _notify_tag_add(tagging)
        _notify_tag_change(type: :after_add, tagging: tagging)
      end

      def _notify_tag_remove(tagging)
        _notify_tag_change(type: :after_remove, tagging: tagging)
      end
  end
  # rubocop:enable Metrics/BlockLength
end