# 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