config/initializers/audited_hacks.rb
module AuditHacks extend ActiveSupport::Concern TTL = 3.months included do # this could also validate provider_id, but unfortunately we have to much going on # and factories destroy the whole thing validates :kind, :presence => true alias_attribute :association_id, :associated_id alias_attribute :association_type, :associated_type after_commit :log_to_stdout, on: :create, if: :logging_to_stdout? def self.delete_old where('created_at < ?', TTL.ago).delete_all end def self.logging_to_stdout? Features::LoggingConfig.config.audits_to_stdout end delegate :logging_to_stdout?, to: :class def log_to_stdout logger.tagged('audit', kind, action) { logger.info log_trail } end def obfuscated dup.tap do |copy| copy.send(:write_attribute, :id, id) copy.send(:write_attribute, :created_at, created_at) copy.audited_changes = ThreeScale::FilterArguments.new(audited_changes).filter if audited_changes end end protected def log_trail to_h_safe.to_json end alias_method :to_s, :log_trail def to_h_safe attrs = %w[auditable_type auditable_id action audited_changes version provider_id user_id user_type request_uuid remote_address created_at] hash = obfuscated.attributes.slice(*attrs) hash['user_role'] = user&.role hash['audit_id'] = id hash end end def audited_changes_for_destroy_list changes = audited_changes.extract!(*kind.constantize.attributes_for_destroy_list) changes.merge('id' => auditable_id) end # runs all before_create callbacks except version, which performs a database call redundant for background auditingAuditHacks#before_background_callbacks has unused parameter 'args' def before_background_callbacks(*args) set_audit_user set_request_uuid set_remote_address end def enqueue_job AuditedWorker.perform_async(attributes.as_json) endend module AuditedHacks extend ActiveSupport::Concern included do extend ClassMethods end module ClassMethods def audited(options = {}) super # this disables auditing only for current thread self.disable_auditing if Rails.env.test? include InstanceMethods include AfterCommitQueue class << self prepend ClassMethods end end def synchronous_auditsAuditedHacks::ClassMethods#synchronous_audits calls 'Thread.current' 3 times original = Thread.current.thread_variable_get(:audit_hacks_synchronous) Thread.current.thread_variable_set(:audit_hacks_synchronous, true) yield if block_given? original ensure Thread.current.thread_variable_set(:audit_hacks_synchronous, original) end # override method to also enable auditing globally def with_auditing auditing_was_enabled = Audited.auditing_enabled Audited.auditing_enabled = true super ensure Audited.auditing_enabled = false unless auditing_was_enabled end def with_synchronous_auditing(&block) synchronous_audits { with_auditing(&block) } end end module InstanceMethods def auditing_enabled? auditing_enabled end private Method `write_audit` has a Cognitive Complexity of 14 (exceeds 5 allowed). Consider refactoring.
AuditedHacks::InstanceMethods#write_audit has approx 19 statements def write_audit(attrs) return unless auditing_enabled AuditedHacks::InstanceMethods#write_audit manually dispatches method call provider_id = respond_to?(:tenant_id) && self.tenant_id provider_id ||= respond_to?(:provider_account_id) && self.provider_account_id provider_id ||= respond_to?(:provider_id) && self.provider_id provider_id ||= respond_to?(:provider_account) && self.provider_account.try!(:id) provider_id ||= self.provider_id_for_audits attrs[:provider_id] = provider_idAuditedHacks::InstanceMethods#write_audit calls 'self.class' 2 times attrs[:kind] = self.class.to_s Audited.audit_class.as_user(User.current) do if self.class.synchronous_audits super else # this is basically a copy of super that enqueues audit instead of creating it self.audit_comment = nil AuditedHacks::InstanceMethods#write_audit performs a nil-check attrs[:associated] = send(audit_associated_with) unless audit_associated_with.nil? run_callbacks(:audit) { audit = audits.build(attrs) audit.before_background_callbacks audit.destroy # avoid autosave logic run_after_commit { audit.enqueue_job } # if needed to combine audits, that can be moved to the background job # combine_audits_if_needed if attrs[:action] != "create" audit } end end end protected # Overwrite this in your auditable models to return something for audit's provider_id # # for example: # class Mojo < ActiveRecord::Base # auditable # # def provider_id_for_audits # 42 # end # end def provider_id_for_audits nil end endend ActiveSupport.on_load(:active_record) do # Unlike above, this disables auditing globally Audited.auditing_enabled = false if Rails.env.test? # we want to audit created_at field Audited.ignored_attributes = %w(lock_version updated_at created_on updated_on) Audited.audit_class.class_eval do include AuditHacks end # This fixes issues with overloading current_user in our controllers Audited::Sweeper.prepend(Module.new docurrent_user doesn't depend on instance state (maybe move it to another class?) def current_user User.current end end) ::ActiveRecord::Base.class_eval do include AuditedHacks endend