You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
87 lines
2.8 KiB
87 lines
2.8 KiB
# frozen_string_literal: true |
|
|
|
module AccountCounters |
|
extend ActiveSupport::Concern |
|
|
|
ALLOWED_COUNTER_KEYS = %i(statuses_count following_count followers_count).freeze |
|
|
|
included do |
|
has_one :account_stat, inverse_of: :account |
|
after_save :save_account_stat |
|
end |
|
|
|
delegate :statuses_count, |
|
:statuses_count=, |
|
:following_count, |
|
:following_count=, |
|
:followers_count, |
|
:followers_count=, |
|
:last_status_at, |
|
to: :account_stat |
|
|
|
# @param [Symbol] key |
|
def increment_count!(key) |
|
update_count!(key, 1) |
|
end |
|
|
|
# @param [Symbol] key |
|
def decrement_count!(key) |
|
update_count!(key, -1) |
|
end |
|
|
|
# @param [Symbol] key |
|
# @param [Integer] value |
|
def update_count!(key, value) |
|
raise ArgumentError, "Invalid key #{key}" unless ALLOWED_COUNTER_KEYS.include?(key) |
|
raise ArgumentError, 'Do not call update_count! on dirty objects' if association(:account_stat).loaded? && account_stat&.changed? && account_stat.changed_attribute_names_to_save == %w(id) |
|
|
|
value = value.to_i |
|
default_value = value.positive? ? value : 0 |
|
|
|
# We do an upsert using manually written SQL, as Rails' upsert method does |
|
# not seem to support writing expressions in the UPDATE clause, but only |
|
# re-insert the provided values instead. |
|
# Even ARel seem to be missing proper handling of upserts. |
|
sql = if value.positive? && key == :statuses_count |
|
<<-SQL.squish |
|
INSERT INTO account_stats(account_id, #{key}, created_at, updated_at, last_status_at) |
|
VALUES (:account_id, :default_value, now(), now(), now()) |
|
ON CONFLICT (account_id) DO UPDATE |
|
SET #{key} = account_stats.#{key} + :value, |
|
last_status_at = now(), |
|
updated_at = now() |
|
RETURNING id; |
|
SQL |
|
else |
|
<<-SQL.squish |
|
INSERT INTO account_stats(account_id, #{key}, created_at, updated_at) |
|
VALUES (:account_id, :default_value, now(), now()) |
|
ON CONFLICT (account_id) DO UPDATE |
|
SET #{key} = account_stats.#{key} + :value, |
|
updated_at = now() |
|
RETURNING id; |
|
SQL |
|
end |
|
|
|
sql = AccountStat.sanitize_sql([sql, account_id: id, default_value: default_value, value: value]) |
|
account_stat_id = AccountStat.connection.exec_query(sql)[0]['id'] |
|
|
|
# Reload account_stat if it was loaded, taking into account newly-created unsaved records |
|
if association(:account_stat).loaded? |
|
account_stat.id = account_stat_id if account_stat.new_record? |
|
account_stat.reload |
|
end |
|
end |
|
|
|
def account_stat |
|
super || build_account_stat |
|
end |
|
|
|
private |
|
|
|
def save_account_stat |
|
return unless association(:account_stat).loaded? && account_stat&.changed? |
|
|
|
account_stat.save |
|
end |
|
end
|
|
|