discourse/app/models/user_badge.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

140 lines
5.0 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2014-03-05 20:52:20 +08:00
class UserBadge < ActiveRecord::Base
DEV: Migrate notifications#id and related columns to bigint (#28584) * DEV: Migrate notifications#id to bigint (#28444) The `notifications.id` column is the most probable column to run out of values. This is because it is an `int` column that has only 2147483647 values and many notifications are generated on a regular basis in an active community. This commit migrates the column to `bigint`. These migrations do not use `ALTER TABLE ... COLUMN ... TYPE` in order to avoid the `ACCESS EXCLUSIVE` lock on the entire table. Instead, they create a new `bigint` column, copy the values to the new column and then sets the new column as primary key. Related columns (see `user_badges`, `shelved_notifications`) will be migrated in a follow-up commit. * DEV: Fix bigint notifications id migration to deal with public schema (#28538) Follow up to 799a45a291e9f2bd94278f565e58874458768079 * DEV: Migrate shelved_notifications#notification_id to bigint (#28549) DEV: Migrate shelved_notifications#notification_id to bigint The `notifications.id` has been migrated to `bigint` in previous commit 799a45a291e9f2bd94278f565e58874458768079. * DEV: Fix annotations (#28569) Follow-up to ec8ba5a0b9ff8dbefae74d6ec4f08dc13e418c6b * DEV: Migrate user_badges#notification_id to bigint (#28546) The `notifications.id` has been migrated to bigint in previous commit 799a45a291e9f2bd94278f565e58874458768079. This commit migrates one of the related columns, `user_badges.notification_id`, to `bigint`. * DEV: Migrate `User#seen_notification_id` to `bigint` (#28572) `Notification#id` was migrated to `bigint` in 799a45a291e9f2bd94278f565e58874458768079 * DEV: Migrate `Chat::NotificationMention#notification_id` to `bigint` (#28571) `Notification#id` was migrated to `bigint` in 799a45a291e9f2bd94278f565e58874458768079 --------- Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2024-08-29 23:06:55 +08:00
self.ignored_columns = [
:old_notification_id, # TODO: Remove when column is dropped. At this point, the migration to drop the column has not been writted.
]
2014-03-05 20:52:20 +08:00
belongs_to :badge
belongs_to :user
belongs_to :granted_by, class_name: "User"
belongs_to :notification, dependent: :destroy
belongs_to :post
2014-03-05 20:52:20 +08:00
BOOLEAN_ATTRIBUTES = %w[is_favorite]
scope :grouped_with_count,
-> do
group(:badge_id, :user_id)
.select_for_grouping
.order("MAX(featured_rank) ASC")
.includes(:user, :granted_by, { badge: :badge_type }, post: :topic)
end
scope :select_for_grouping,
-> do
select(
UserBadge.attribute_names.map do |name|
operation = BOOLEAN_ATTRIBUTES.include?(name) ? "BOOL_OR" : "MAX"
"#{operation}(user_badges.#{name}) AS #{name}"
end,
'COUNT(*) AS "count"',
)
end
scope :for_enabled_badges,
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
2014-03-05 20:52:20 +08:00
validates :user_id, presence: true
validates :granted_at, presence: true
validates :granted_by, presence: true
after_create do
Badge.increment_counter "grant_count", self.badge_id
UserStat.update_distinct_badge_count self.user_id
UserBadge.update_featured_ranks! self.user_id
self.trigger_user_badge_granted_event
end
after_destroy do
Badge.decrement_counter "grant_count", self.badge_id
UserStat.update_distinct_badge_count self.user_id
UserBadge.update_featured_ranks! self.user_id
DiscourseEvent.trigger(:user_badge_removed, self.badge_id, self.user_id)
DiscourseEvent.trigger(:user_badge_revoked, user_badge: self)
end
def self.ensure_consistency!
self.update_featured_ranks!
end
def self.update_featured_ranks!(user_id = nil)
query = <<~SQL
WITH featured_tl_badge AS -- Find the best trust level badge for each user
(
SELECT user_id, max(badge_id) as badge_id
FROM user_badges
WHERE badge_id IN (1,2,3,4)
#{"AND user_id = #{user_id.to_i}" if user_id}
GROUP BY user_id
),
ranks AS ( -- Take all user badges, group by user_id and badge_id, and calculate a rank for each one
SELECT
user_badges.user_id,
user_badges.badge_id,
RANK() OVER (
PARTITION BY user_badges.user_id -- Do a separate rank for each user
ORDER BY BOOL_OR(badges.enabled) DESC, -- Disabled badges last
MAX(featured_tl_badge.user_id) NULLS LAST, -- Best tl badge first
BOOL_OR(user_badges.is_favorite) DESC NULLS LAST, -- Favorite badges next
CASE WHEN user_badges.badge_id IN (1,2,3,4) THEN 1 ELSE 0 END ASC, -- Non-featured tl badges last
MAX(badges.badge_type_id) ASC,
MAX(badges.grant_count) ASC,
user_badges.badge_id DESC
) rank_number
FROM user_badges
INNER JOIN badges ON badges.id = user_badges.badge_id
LEFT JOIN featured_tl_badge ON featured_tl_badge.user_id = user_badges.user_id AND featured_tl_badge.badge_id = user_badges.badge_id
#{"WHERE user_badges.user_id = #{user_id.to_i}" if user_id}
GROUP BY user_badges.user_id, user_badges.badge_id
)
-- Now use that data to update the featured_rank column
UPDATE user_badges SET featured_rank = rank_number
FROM ranks WHERE ranks.badge_id = user_badges.badge_id AND ranks.user_id = user_badges.user_id AND featured_rank IS DISTINCT FROM rank_number
SQL
DB.exec query
end
def self.trigger_user_badge_granted_event(badge_id, user_id)
DiscourseEvent.trigger(:user_badge_granted, badge_id, user_id)
end
private
def trigger_user_badge_granted_event
self.class.trigger_user_badge_granted_event(self.badge_id, self.user_id)
end
def single_grant_badge?
2019-04-23 18:25:21 +08:00
self.badge ? self.badge.single_grant? : true
end
2014-03-05 20:52:20 +08:00
end
# == Schema Information
#
# Table name: user_badges
#
# id :integer not null, primary key
# badge_id :integer not null
# user_id :integer not null
# granted_at :datetime not null
# granted_by_id :integer not null
# post_id :integer
# seq :integer default(0), not null
# featured_rank :integer
2020-04-28 18:29:39 +08:00
# created_at :datetime not null
# is_favorite :boolean
DEV: Migrate notifications#id and related columns to bigint (#28584) * DEV: Migrate notifications#id to bigint (#28444) The `notifications.id` column is the most probable column to run out of values. This is because it is an `int` column that has only 2147483647 values and many notifications are generated on a regular basis in an active community. This commit migrates the column to `bigint`. These migrations do not use `ALTER TABLE ... COLUMN ... TYPE` in order to avoid the `ACCESS EXCLUSIVE` lock on the entire table. Instead, they create a new `bigint` column, copy the values to the new column and then sets the new column as primary key. Related columns (see `user_badges`, `shelved_notifications`) will be migrated in a follow-up commit. * DEV: Fix bigint notifications id migration to deal with public schema (#28538) Follow up to 799a45a291e9f2bd94278f565e58874458768079 * DEV: Migrate shelved_notifications#notification_id to bigint (#28549) DEV: Migrate shelved_notifications#notification_id to bigint The `notifications.id` has been migrated to `bigint` in previous commit 799a45a291e9f2bd94278f565e58874458768079. * DEV: Fix annotations (#28569) Follow-up to ec8ba5a0b9ff8dbefae74d6ec4f08dc13e418c6b * DEV: Migrate user_badges#notification_id to bigint (#28546) The `notifications.id` has been migrated to bigint in previous commit 799a45a291e9f2bd94278f565e58874458768079. This commit migrates one of the related columns, `user_badges.notification_id`, to `bigint`. * DEV: Migrate `User#seen_notification_id` to `bigint` (#28572) `Notification#id` was migrated to `bigint` in 799a45a291e9f2bd94278f565e58874458768079 * DEV: Migrate `Chat::NotificationMention#notification_id` to `bigint` (#28571) `Notification#id` was migrated to `bigint` in 799a45a291e9f2bd94278f565e58874458768079 --------- Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
2024-08-29 23:06:55 +08:00
# notification_id :bigint
2014-03-05 20:52:20 +08:00
#
# Indexes
#
2014-07-15 09:29:44 +08:00
# index_user_badges_on_badge_id_and_user_id (badge_id,user_id)
2018-07-16 14:18:07 +08:00
# index_user_badges_on_badge_id_and_user_id_and_post_id (badge_id,user_id,post_id) UNIQUE WHERE (post_id IS NOT NULL)
# index_user_badges_on_badge_id_and_user_id_and_seq (badge_id,user_id,seq) UNIQUE WHERE (post_id IS NULL)
2019-01-12 03:29:56 +08:00
# index_user_badges_on_user_id (user_id)
2014-03-05 20:52:20 +08:00
#