FIX: Update category tag stats with new or deleted (#21531)

The old method updated only existing records, without considering that
new tags might have been created or some tags might not exist anymore.
This was usually not a problem because the stats were also updated by
other code paths.

However, the ensure consistency job should be more solid and help when
other code paths fail or after importing data.

Also, update category tag stats too should happen when updating other
category stats as well.
This commit is contained in:
Bianca Nenciu 2023-05-18 11:46:44 +02:00 committed by GitHub
parent 809bab5782
commit f0ec1fad8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 10 deletions

View File

@ -44,23 +44,38 @@ class CategoryTagStat < ActiveRecord::Base
# Recalculate all topic counts if they got out of sync # Recalculate all topic counts if they got out of sync
def self.update_topic_counts def self.update_topic_counts
# Add new records or update existing records
DB.exec <<~SQL DB.exec <<~SQL
UPDATE category_tag_stats stats WITH stats AS (
SET topic_count = x.topic_count SELECT topics.category_id as category_id,
FROM (
SELECT COUNT(topics.id) AS topic_count,
tags.id AS tag_id, tags.id AS tag_id,
topics.category_id as category_id COUNT(topics.id) AS topic_count
FROM tags FROM tags
INNER JOIN topic_tags ON tags.id = topic_tags.tag_id INNER JOIN topic_tags ON tags.id = topic_tags.tag_id
INNER JOIN topics ON topics.id = topic_tags.topic_id INNER JOIN topics ON topics.id = topic_tags.topic_id
AND topics.deleted_at IS NULL AND topics.deleted_at IS NULL
AND topics.category_id IS NOT NULL AND topics.category_id IS NOT NULL
GROUP BY tags.id, topics.category_id GROUP BY topics.category_id, tags.id
) x )
WHERE stats.tag_id = x.tag_id INSERT INTO category_tag_stats(category_id, tag_id, topic_count)
AND stats.category_id = x.category_id SELECT category_id, tag_id, topic_count FROM stats
AND x.topic_count <> stats.topic_count ON CONFLICT (category_id, tag_id) DO
UPDATE SET topic_count = EXCLUDED.topic_count
SQL
# Delete old records
DB.exec <<~SQL
DELETE FROM category_tag_stats
WHERE (category_id, tag_id) NOT IN (
SELECT topics.category_id as category_id,
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.category_id IS NOT NULL
GROUP BY topics.category_id, tags.id
)
SQL SQL
end end
end end

View File

@ -24,6 +24,7 @@ task "categories:move_topics", %i[from_category to_category] => [:environment] d
puts "Updating category stats..." puts "Updating category stats..."
Category.update_stats Category.update_stats
CategoryTagStat.update_topic_counts
end end
puts "", "Done!", "" puts "", "Done!", ""

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
describe CategoryTagStat do
fab!(:category) { Fabricate(:category) }
fab!(:tag) { Fabricate(:tag) }
fab!(:topic) { Fabricate(:topic, category: category, tags: [tag]) }
describe "#update_topic_counts" do
it "creates new records" do
CategoryTagStat.destroy_all
expect { CategoryTagStat.update_topic_counts }.to change { CategoryTagStat.count }.by(1)
category_tag_stat = CategoryTagStat.last
expect(category_tag_stat.category_id).to eq(category.id)
expect(category_tag_stat.tag_id).to eq(tag.id)
expect(category_tag_stat.topic_count).to eq(1)
end
it "updates existing records" do
CategoryTagStat.last.update(topic_count: 10)
expect { CategoryTagStat.update_topic_counts }.not_to change { CategoryTagStat.count }
category_tag_stat = CategoryTagStat.last
expect(category_tag_stat.category_id).to eq(category.id)
expect(category_tag_stat.tag_id).to eq(tag.id)
expect(category_tag_stat.topic_count).to eq(1)
end
it "deletes old records" do
CategoryTagStat.last.update(tag_id: Fabricate(:tag).id)
expect { CategoryTagStat.update_topic_counts }.not_to change { CategoryTagStat.count }
category_tag_stat = CategoryTagStat.last
expect(category_tag_stat.category_id).to eq(category.id)
expect(category_tag_stat.tag_id).to eq(tag.id)
expect(category_tag_stat.topic_count).to eq(1)
end
end
end