mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 19:01:52 +08:00
20a17f248c
In the past, CategoryFeaturedTopic.feature_topics raised an exception sometimes because it tried to create multiple CategoryFeaturedTopic records for the same topic. This code should not raise any exceptions as long as the list of new topic IDs is unique because all previous records are deleted first, then recreated and everything happens inside a transaction. The previous rescue block was dead code anyway because it tried to catch PG::UniqueViolation instead of ActiveRecord::RecordNotUnique. This commit includes a fix to ensure that the topic IDs are unique.
97 lines
3.0 KiB
Ruby
97 lines
3.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class CategoryFeaturedTopic < ActiveRecord::Base
|
|
belongs_to :category
|
|
belongs_to :topic
|
|
|
|
NEXT_CATEGORY_ID_KEY = 'category-featured-topic:next-category-id'
|
|
DEFAULT_BATCH_SIZE = 100
|
|
|
|
# Populates the category featured topics.
|
|
def self.feature_topics(batched: false, batch_size: nil)
|
|
current = {}
|
|
CategoryFeaturedTopic.select(:topic_id, :category_id).order(:rank).each do |f|
|
|
(current[f.category_id] ||= []) << f.topic_id
|
|
end
|
|
|
|
batch_size ||= DEFAULT_BATCH_SIZE
|
|
|
|
next_category_id = batched ? Discourse.redis.get(NEXT_CATEGORY_ID_KEY).to_i : 0
|
|
|
|
categories = Category.select(:id, :topic_id, :num_featured_topics)
|
|
.where('id >= ?', next_category_id)
|
|
.order('id ASC')
|
|
.limit(batch_size)
|
|
.to_a
|
|
|
|
if batched
|
|
if categories.length == batch_size
|
|
next_id = Category.where('id > ?', categories.last.id).order('id asc').limit(1).pluck(:id)[0]
|
|
next_id ? Discourse.redis.setex(NEXT_CATEGORY_ID_KEY, 1.day, next_id) : clear_batch!
|
|
else
|
|
clear_batch!
|
|
end
|
|
end
|
|
|
|
categories.each do |c|
|
|
CategoryFeaturedTopic.feature_topics_for(c, current[c.id] || [])
|
|
end
|
|
end
|
|
|
|
def self.clear_batch!
|
|
Discourse.redis.del(NEXT_CATEGORY_ID_KEY)
|
|
end
|
|
|
|
def self.feature_topics_for(c, existing = nil)
|
|
return if c.blank?
|
|
|
|
query_opts = {
|
|
per_page: c.num_featured_topics,
|
|
except_topic_ids: [c.topic_id],
|
|
visible: true,
|
|
no_definitions: true
|
|
}
|
|
|
|
# It may seem a bit odd that we are running 2 queries here, when admin
|
|
# can clearly pull out all the topics needed.
|
|
# We do so, so anonymous will ALWAYS get some topics
|
|
# If we only fetched as admin we may have a situation where anon can see
|
|
# no featured topics (all the previous 2x topics are only visible to admins)
|
|
|
|
# Add topics, even if they're in secured categories or invisible
|
|
query = TopicQuery.new(Discourse.system_user, query_opts)
|
|
results = query.list_category_topic_ids(c)
|
|
|
|
# Add some topics that are visible to everyone:
|
|
anon_query = TopicQuery.new(nil, query_opts.merge(except_topic_ids: [c.topic_id] + results))
|
|
results += anon_query.list_category_topic_ids(c)
|
|
|
|
results.uniq!
|
|
return if results == existing
|
|
|
|
CategoryFeaturedTopic.transaction do
|
|
CategoryFeaturedTopic.where(category_id: c.id).delete_all
|
|
results.each_with_index do |topic_id, idx|
|
|
c.category_featured_topics.create(topic_id: topic_id, rank: idx)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: category_featured_topics
|
|
#
|
|
# category_id :integer not null
|
|
# topic_id :integer not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# rank :integer default(0), not null
|
|
# id :integer not null, primary key
|
|
#
|
|
# Indexes
|
|
#
|
|
# cat_featured_threads (category_id,topic_id) UNIQUE
|
|
# index_category_featured_topics_on_category_id_and_rank (category_id,rank)
|
|
#
|