Class: Tag

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
HasDestroyedWebHook, HasSanitizableFields, Searchable
Defined in:
app/models/tag.rb

Constant Summary collapse

RESERVED_TAGS =
[
  "none",
  "constructor", # prevents issues with javascript's constructor of objects
]

Constants included from Searchable

Searchable::PRIORITIES

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HasSanitizableFields

#sanitize_field

Methods included from HasDestroyedWebHook

#enqueue_destroyed_web_hook

Class Method Details

.ensure_consistency!Object



72
73
74
# File 'app/models/tag.rb', line 72

def self.ensure_consistency!
  update_topic_counts
end

.find_by_name(name) ⇒ Object



134
135
136
# File 'app/models/tag.rb', line 134

def self.find_by_name(name)
  self.find_by("lower(name) = ?", name.downcase)
end

.include_tags?Boolean

Returns:

  • (Boolean)


205
206
207
# File 'app/models/tag.rb', line 205

def self.include_tags?
  SiteSetting.tagging_enabled
end

.pm_tags(limit: 1000, guardian: nil, allowed_user: nil) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/models/tag.rb', line 178

def self.pm_tags(limit: 1000, guardian: nil, allowed_user: nil)
  return [] if allowed_user.blank? || !(guardian || Guardian.new).can_tag_pms?
  user_id = allowed_user.id

  DB.query_hash(<<~SQL).map!(&:symbolize_keys!)
    SELECT tags.name as id, tags.name as text, COUNT(topics.id) AS count
      FROM tags
      JOIN topic_tags ON tags.id = topic_tags.tag_id
      JOIN topics ON topics.id = topic_tags.topic_id
                 AND topics.deleted_at IS NULL
                 AND topics.archetype = 'private_message'
     WHERE topic_tags.topic_id IN (
        SELECT topic_id
          FROM topic_allowed_users
         WHERE user_id = #{user_id.to_i}
         UNION
        SELECT tg.topic_id
          FROM topic_allowed_groups tg
          JOIN group_users gu ON gu.user_id = #{user_id.to_i}
                             AND gu.group_id = tg.group_id
     )
     GROUP BY tags.name
     ORDER BY count DESC
     LIMIT #{limit.to_i}
  SQL
end

.top_tags(limit_arg: nil, category: nil, guardian: Guardian.new) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'app/models/tag.rb', line 138

def self.top_tags(limit_arg: nil, category: nil, guardian: Guardian.new)
  # we add 1 to max_tags_in_filter_list to efficiently know we have more tags
  # than the limit. Frontend is responsible to enforce limit.
  limit = limit_arg || (SiteSetting.max_tags_in_filter_list + 1)
  scope_category_ids = guardian.allowed_category_ids
  scope_category_ids &= ([category.id] + category.subcategories.pluck(:id)) if category

  return [] if scope_category_ids.empty?

  filter_sql =
    (
      if guardian.is_staff?
        ""
      else
        " AND tags.id IN (#{DiscourseTagging.visible_tags(guardian).select(:id).to_sql})"
      end
    )

  tag_names_with_counts = DB.query <<~SQL
    SELECT tags.name as tag_name, SUM(stats.topic_count) AS sum_topic_count
      FROM category_tag_stats stats
      JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0
     WHERE stats.category_id in (#{scope_category_ids.join(",")})
     #{filter_sql}
  GROUP BY tags.name
  ORDER BY sum_topic_count DESC, tag_name ASC
     LIMIT #{limit}
  SQL

  tag_names_with_counts.map { |row| row.tag_name }
end

.topic_count_column(guardian) ⇒ Object



170
171
172
173
174
175
176
# File 'app/models/tag.rb', line 170

def self.topic_count_column(guardian)
  if guardian&.is_staff? || SiteSetting.include_secure_categories_in_tag_counts
    "staff_topic_count"
  else
    "public_topic_count"
  end
end

.update_topic_countsObject



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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'app/models/tag.rb', line 76

def self.update_topic_counts
  DB.exec <<~SQL
    UPDATE tags t
       SET staff_topic_count = x.topic_count
      FROM (
           SELECT COUNT(topics.id) AS topic_count, tags.id AS tag_id
             FROM tags
        LEFT JOIN topic_tags ON tags.id = topic_tags.tag_id
        LEFT JOIN topics ON topics.id = topic_tags.topic_id
                        AND topics.deleted_at IS NULL
                        AND topics.archetype != 'private_message'
         GROUP BY tags.id
      ) x
     WHERE x.tag_id = t.id
       AND x.topic_count <> t.staff_topic_count
  SQL

  DB.exec <<~SQL
    UPDATE tags t
    SET public_topic_count = x.topic_count
    FROM (
      WITH tags_with_public_topics AS (
        SELECT
          COUNT(topics.id) AS topic_count,
          tags.id AS tag_id
        FROM tags
        INNER JOIN topic_tags ON tags.id = topic_tags.tag_id
        INNER JOIN topics ON topics.id = topic_tags.topic_id AND topics.deleted_at IS NULL AND topics.archetype != 'private_message'
        INNER JOIN categories ON categories.id = topics.category_id AND NOT categories.read_restricted
        GROUP BY tags.id
      )
      SELECT
        COALESCE(tags_with_public_topics.topic_count, 0 ) AS topic_count,
        tags.id AS tag_id
      FROM tags
      LEFT JOIN tags_with_public_topics ON tags_with_public_topics.tag_id = tags.id
    ) x
    WHERE x.tag_id = t.id
    AND x.topic_count <> t.public_topic_count;
  SQL

  DB.exec <<~SQL
    UPDATE tags t
       SET pm_topic_count = x.pm_topic_count
      FROM (
           SELECT COUNT(topics.id) AS pm_topic_count, tags.id AS tag_id
             FROM tags
        LEFT JOIN topic_tags ON tags.id = topic_tags.tag_id
        LEFT JOIN topics ON topics.id = topic_tags.topic_id
                        AND topics.deleted_at IS NULL
                        AND topics.archetype = 'private_message'
         GROUP BY tags.id
      ) x
     WHERE x.tag_id = t.id
       AND x.pm_topic_count <> t.pm_topic_count
  SQL
end

Instance Method Details

#all_categories(guardian) ⇒ Object



252
253
254
255
256
# File 'app/models/tag.rb', line 252

def all_categories(guardian)
  categories = Category.secured(guardian).where(id: all_category_ids)
  Category.preload_user_fields!(guardian, categories)
  categories
end

#all_category_idsObject



246
247
248
249
250
# File 'app/models/tag.rb', line 246

def all_category_ids
  @all_category_ids ||=
    categories.pluck(:id) +
      tag_groups.includes(:categories).flat_map { |tg| tg.categories.map(&:id) }
end

#full_urlObject



215
216
217
# File 'app/models/tag.rb', line 215

def full_url
  "#{Discourse.base_url}/tag/#{UrlHelper.encode_component(self.name)}"
end

#index_searchObject



219
220
221
# File 'app/models/tag.rb', line 219

def index_search
  SearchIndexer.index(self)
end

#synonym?Boolean

Returns:

  • (Boolean)


223
224
225
# File 'app/models/tag.rb', line 223

def synonym?
  !self.target_tag_id.nil?
end

#target_tag_validatorObject



227
228
229
230
231
232
233
# File 'app/models/tag.rb', line 227

def target_tag_validator
  if synonyms.exists?
    errors.add(:target_tag_id, I18n.t("tags.synonyms_exist"))
  elsif target_tag&.synonym?
    errors.add(:target_tag_id, I18n.t("tags.invalid_target_tag"))
  end
end

#update_synonym_associationsObject



235
236
237
238
239
240
241
242
243
244
# File 'app/models/tag.rb', line 235

def update_synonym_associations
  if target_tag_id && saved_change_to_target_tag_id?
    target_tag.tag_groups.each do |tag_group|
      tag_group.tags << self if tag_group.tags.exclude?(self)
    end
    target_tag.categories.each do |category|
      category.tags << self if category.tags.exclude?(self)
    end
  end
end

#urlObject Also known as: relative_url



209
210
211
# File 'app/models/tag.rb', line 209

def url
  "#{Discourse.base_path}/tag/#{UrlHelper.encode_component(self.name)}"
end