1# frozen_string_literal: true 2 3# This module is providing helpers for updating `ProjectStatistics` with `after_save` and `before_destroy` hooks. 4# 5# It deals with `ProjectStatistics.increment_statistic` making sure not to update statistics on a cascade delete from the 6# project, and keeping track of value deltas on each save. It updates the DB only when a change is needed. 7# 8# Example: 9# 10# module Ci 11# class JobArtifact < ApplicationRecord 12# include UpdateProjectStatistics 13# 14# update_project_statistics project_statistics_name: :build_artifacts_size 15# end 16# end 17# 18# Expectation: 19# 20# - `statistic_attribute` must be an ActiveRecord attribute 21# - The model must implement `project` and `project_id`. i.e. direct Project relationship or delegation 22module UpdateProjectStatistics 23 extend ActiveSupport::Concern 24 include AfterCommitQueue 25 26 class_methods do 27 attr_reader :project_statistics_name, :statistic_attribute 28 29 # Configure the model to update `project_statistics_name` on ProjectStatistics, 30 # when `statistic_attribute` changes 31 # 32 # - project_statistics_name: A column of `ProjectStatistics` to update 33 # - statistic_attribute: An attribute of the current model, default to `size` 34 def update_project_statistics(project_statistics_name:, statistic_attribute: :size) 35 @project_statistics_name = project_statistics_name 36 @statistic_attribute = statistic_attribute 37 38 after_save(:update_project_statistics_after_save, if: :update_project_statistics_after_save?) 39 after_destroy(:update_project_statistics_after_destroy, if: :update_project_statistics_after_destroy?) 40 end 41 42 private :update_project_statistics 43 end 44 45 included do 46 private 47 48 def update_project_statistics_after_save? 49 update_project_statistics_attribute_changed? 50 end 51 52 def update_project_statistics_after_destroy? 53 !project_destroyed? 54 end 55 56 def update_project_statistics_after_save 57 attr = self.class.statistic_attribute 58 delta = read_attribute(attr).to_i - attribute_before_last_save(attr).to_i 59 60 schedule_update_project_statistic(delta) 61 end 62 63 def update_project_statistics_attribute_changed? 64 saved_change_to_attribute?(self.class.statistic_attribute) 65 end 66 67 def update_project_statistics_after_destroy 68 delta = -read_attribute(self.class.statistic_attribute).to_i 69 70 schedule_update_project_statistic(delta) 71 end 72 73 def project_destroyed? 74 project.pending_delete? 75 end 76 77 def schedule_update_project_statistic(delta) 78 return if delta == 0 79 return if project.nil? 80 81 run_after_commit do 82 ProjectStatistics.increment_statistic( 83 project, self.class.project_statistics_name, delta) 84 end 85 end 86 end 87end 88