app/workers/stat_worker.rb
# frozen_string_literal: true
class StatWorker
include Sidekiq::Worker
# @param stat [String] the class name of the stat to act upon
# @param user_id [Integer] the ID of the user whose stat we are modifying
# @param action ['update','create','destroy'] the action to perform
# @param model [Hash] the model data to rehydrate on the worker
# @option model [String] 'class' the class name of the model to rehydrate into
# @option model [Hash] 'attributes' the attributes to rehydrate into the model
# @param changes [Hash<String,Array<Object>>] a list of change state to apply on top of the model
def perform(stat, user_id, action, model, changes)
# Bail if the User has stopped existing (deletion)
return unless User.exists?(user_id)
# Rehydrate the model
model_class, model_attributes = model.values_at('class', 'attributes')
model = model_class.constantize.new(model_attributes)
# Wrap the model in a DirtyChangeWrapper to make it appear dirty
wrapper = DirtyChangeWrapper.new(model, changes)
# Lock the User's stat row so no other workers can update it while we are
Stat.transaction do
stat = stat.constantize.for_user(user_id)
stat.save!
stat.lock!
# If the model data is newer than the last stat recalculation, inform it of the change
if model.updated_at > stat.recalculated_at && stat.respond_to?("on_#{action}")
# Call down to the stat to run the action
stat.public_send("on_#{action}", wrapper)
end
end
end
# @see #perform
def self.perform_async(stat, user, action, model)
attributes = model.attributes
# Destruction doesn't set the update time which we need to account for recalculation
attributes['updated_at'] = Time.now if action.to_s == 'destroy'
changes = model.saved_changes
super(
stat,
user.id,
action.to_s,
{ class: model.class.name.to_s, attributes: }.as_json,
changes.as_json
)
end
end