discourse/app/models/topic_view_item.rb
David Taylor 3332e9f4c3
DEV: Always pass --force to annotaterb and reorder annotations (#39977)
`.annotaterb.yml` has carried `classified_sort: true` since the project
switched from `annotate` to `annotaterb` (commit 0eab7daea4, July 2025),
but annotaterb's default behaviour is to compare the existing schema
block against what it would generate and skip the rewrite when the
column list matches — even when the *ordering* of those columns differs.
The result is that models which haven't had a schema change since the
config landed never get reordered, and `classified_sort` drift
accumulates indefinitely.

`--force` makes annotaterb always rewrite, so a single `bin/rake
annotate:clean` run brings every model into the canonical format and
keeps them there. Every schema block is now grouped primary-key →
regular columns → timestamps → foreign keys (alphabetical within each
group). Pure annotation comment change — no code modifications.

Also cleans up the rake task to avoid string interpolation for `system`
calls.
2026-05-13 14:12:48 +01:00

82 lines
2.4 KiB
Ruby
Vendored

# frozen_string_literal: true
require "ipaddr"
# awkward TopicView is taken
class TopicViewItem < ActiveRecord::Base
self.table_name = "topic_views"
belongs_to :user
belongs_to :topic
validates :topic_id, :ip_address, :viewed_at, presence: true
def self.add(topic_id, ip, user_id = nil, at = nil, skip_redis = false)
# Only store a view once per day per thing per (user || ip)
at ||= Time.zone.today
redis_key = +"view:#{topic_id}:#{at}"
if user_id
redis_key << ":user-#{user_id}"
else
redis_key << ":ip-#{ip}"
end
if skip_redis || Discourse.redis.setnx(redis_key, "1")
skip_redis || Discourse.redis.expire(redis_key, SiteSetting.topic_view_duration_hours.hours)
TopicViewItem.transaction do
# this is called real frequently, working hard to avoid exceptions
sql = <<~SQL
INSERT INTO topic_views (topic_id, ip_address, viewed_at, user_id)
SELECT :topic_id, :ip_address, :viewed_at, :user_id
WHERE NOT EXISTS (
SELECT 1 FROM topic_views
/*where*/
)
SQL
builder = DB.build(sql)
if !user_id
builder.where("ip_address = :ip_address AND topic_id = :topic_id AND user_id IS NULL")
else
builder.where("user_id = :user_id AND topic_id = :topic_id")
ip = nil # do not store IP of logged in users
end
result = builder.exec(topic_id: topic_id, ip_address: ip, viewed_at: at, user_id: user_id)
if result > 0
if user_id
UserStat.where(user_id: user_id).update_all "topics_entered = topics_entered + 1"
end
end
Topic.where(id: topic_id).update_all "views = views + 1"
TopicViewStat.add(
topic_id: topic_id,
date: at,
anonymous_views: user_id ? 0 : 1,
logged_in_views: user_id ? 1 : 0,
)
end
end
end
end
# == Schema Information
#
# Table name: topic_views
#
# ip_address :inet
# viewed_at :date not null
# topic_id :integer not null
# user_id :integer
#
# Indexes
#
# index_topic_views_on_topic_id_and_viewed_at (topic_id,viewed_at)
# index_topic_views_on_user_id_and_viewed_at (user_id,viewed_at)
# index_topic_views_on_viewed_at_and_topic_id (viewed_at,topic_id)
# uniq_ip_or_user_id_topic_views (user_id,ip_address,topic_id) UNIQUE
#