File: taggable.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (114 lines) | stat: -rw-r--r-- 3,311 bytes parent folder | download
1
2
3
4
5
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
106
107
108
109
110
111
112
113
114
# frozen_string_literal: true

module Ci
  module Taggable
    extend ActiveSupport::Concern
    include Gitlab::Utils::StrongMemoize

    included do
      after_save :save_tags

      # rubocop:disable Cop/ActiveRecordDependent -- existing
      has_many :tag_taggings, -> { includes(:tag).where(context: :tags) }, # rubocop:disable Rails/InverseOf -- existing
        as: :taggable,
        class_name: 'Ci::Tagging',
        dependent: :destroy,
        after_add: :dirtify_tag_list,
        after_remove: :dirtify_tag_list

      has_many :tags,
        class_name: 'Ci::Tag',
        through: :tag_taggings,
        source: :tag,
        after_add: :dirtify_tag_list,
        after_remove: :dirtify_tag_list

      has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::Ci::Tagging'
      has_many :base_tags, through: :taggings, source: :tag, class_name: '::Ci::Tag'
      # rubocop:enable Cop/ActiveRecordDependent -- existing

      attribute :tag_list, Gitlab::Database::Type::TagListType.new

      scope :tagged_with, ->(tags, like_search_enabled: false) do
        Gitlab::Ci::Tags::Parser
          .new(tags)
          .parse
          .map { |tag| with_tag(tag, like_search_enabled: like_search_enabled) }
          .reduce(:and)
      end

      scope :with_tag, ->(name, like_search_enabled: false) do
        query = Tagging
                  .merge(unscoped.scoped_tagging)
                  .where(context: :tags)

        if like_search_enabled
          query = query.where(tag_id: Tag.where("name LIKE ?", "%#{sanitize_sql_like(name)}%")) # rubocop:disable GitlabSecurity/SqlInjection -- we are sanitizing
        else
          query = query.where(tag_id: Tag.where(name: name))
        end

        where_exists(query)
      end

      scope :scoped_tagging, -> do
        where(arel_table[primary_key].eq(Tagging.arel_table[:taggable_id]))
          .where(Tagging.arel_table[:taggable_type].eq(base_class.name))
      end
    end

    def tag_list
      Gitlab::Ci::Tags::TagList.new(context_tags.map(&:name))
    end
    strong_memoize_attr :tag_list

    def tag_list=(new_tags)
      parsed_new_list = Gitlab::Ci::Tags::Parser.new(new_tags).parse
      write_attribute('tag_list', parsed_new_list)
      instance_variable_set(:@tag_list, parsed_new_list)
    end

    def reload(*args)
      clear_memoization(:tag_list)
      super(*args)
    end

    private

    def dirtify_tag_list(_tag)
      attribute_will_change!(:tag_list)
      clear_memoization(:tag_list)
    end

    def context_tags
      base_tags.where(taggings: { context: :tags, tagger_id: nil })
    end

    def tag_list_cache_set?
      strong_memoized?(:tag_list)
    end

    def save_tags
      return unless tag_list_cache_set?

      tags = find_or_create_tags_from_list(tag_list.uniq)
      current_tags = context_tags
      old_tags = current_tags - tags
      new_tags = tags - current_tags

      taggings.by_context(:tags).where(tag_id: old_tags).delete_all if old_tags.present?

      new_tags.each do |tag|
        taggings.create!(tag_id: tag.id, context: 'tags', taggable: self)
      end

      yield(new_tags, old_tags) if block_given?

      true
    end

    def find_or_create_tags_from_list(tags)
      Ci::Tag.find_or_create_all_with_like_by_name(tags)
    end
  end
end