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
def self.update_topic_counts
# Add new records or update existing records
DB.exec <<~SQL
UPDATE category_tag_stats stats
SET topic_count = x.topic_count
FROM (
SELECT COUNT(topics.id) AS topic_count,
WITH stats AS (
SELECT topics.category_id as category_id,
tags.id AS tag_id,
topics.category_id as category_id
COUNT(topics.id) AS topic_count
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 tags.id, topics.category_id
) x
WHERE stats.tag_id = x.tag_id
AND stats.category_id = x.category_id
AND x.topic_count <> stats.topic_count
GROUP BY topics.category_id, tags.id
)
INSERT INTO category_tag_stats(category_id, tag_id, topic_count)
SELECT category_id, tag_id, topic_count FROM stats
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
end
end

View File

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