diff --git a/lib/audited/audit.rb b/lib/audited/audit.rb index eadd0fb0..a62ffcf6 100644 --- a/lib/audited/audit.rb +++ b/lib/audited/audit.rb @@ -16,30 +16,39 @@ module Audited # class YAMLIfTextColumnType - class << self - def load(obj) - if text_column? - ActiveRecord::Coders::YAMLColumn.new(Object).load(obj) - else - obj - end - end + def initialize(model) + @model = model + end - def dump(obj) - if text_column? - ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj) - else - obj - end + def load(obj) + if text_column? + ActiveRecord::Coders::YAMLColumn.new(Object).load(obj) + else + obj end + end - def text_column? - Audited.audit_class.columns_hash["audited_changes"].type.to_s == "text" + def dump(obj) + if text_column? + ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj) + else + obj end end + + private + + def text_column? + @model.columns_hash["audited_changes"].type.to_s == "text" + end end class Audit < ::ActiveRecord::Base + def self.inherited(klass) + super + klass.serialize :audited_changes, YAMLIfTextColumnType.new(klass) + end + belongs_to :auditable, polymorphic: true belongs_to :user, polymorphic: true belongs_to :associated, polymorphic: true @@ -49,7 +58,7 @@ class Audit < ::ActiveRecord::Base cattr_accessor :audited_class_names self.audited_class_names = Set.new - serialize :audited_changes, YAMLIfTextColumnType + serialize :audited_changes, YAMLIfTextColumnType.new(self) scope :ascending, -> { reorder(version: :asc) } scope :descending, -> { reorder(version: :desc) } diff --git a/lib/audited/auditor.rb b/lib/audited/auditor.rb index d7d674fe..c9cf92c7 100644 --- a/lib/audited/auditor.rb +++ b/lib/audited/auditor.rb @@ -19,6 +19,10 @@ module Auditor #:nodoc: CALLBACKS = [:audit_create, :audit_update, :audit_destroy] module ClassMethods + def audit_class + audit_class_name&.safe_constantize || Audited.audit_class + end + # == Configuration options # # @@ -67,20 +71,23 @@ def audited(options = {}) class_attribute :audit_associated_with, instance_writer: false class_attribute :audited_options, instance_writer: false + class_attribute :audit_class_name, instance_writer: false + attr_accessor :audit_version, :audit_comment self.audited_options = options normalize_audited_options self.audit_associated_with = audited_options[:associated_with] + self.audit_class_name = audited_options[:audit_class_name] if audited_options[:comment_required] validate :presence_of_audit_comment before_destroy :require_comment if audited_options[:on].include?(:destroy) end - has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name, inverse_of: :auditable - Audited.audit_class.audited_class_names << to_s + has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: audit_class.name, inverse_of: :auditable + audit_class.audited_class_names << to_s after_create :audit_create if audited_options[:on].include?(:create) before_update :audit_update if audited_options[:on].include?(:update) @@ -96,14 +103,18 @@ def audited(options = {}) enable_auditing end - def has_associated_audits - has_many :associated_audits, as: :associated, class_name: Audited.audit_class.name + def has_associated_audits(audit_class_name: Audited.audit_class.name) + has_many :associated_audits, as: :associated, class_name: audit_class_name end end module AuditedInstanceMethods REDACTED = "[REDACTED]" + def audit_class + self.class.audit_class + end + # Temporarily turns off auditing while saving. def save_without_auditing without_auditing { save } @@ -159,14 +170,14 @@ def revisions(from_version = 1) # Returns nil for versions greater than revisions count def revision(version) if version == :previous || audits.last.version >= version - revision_with Audited.audit_class.reconstruct_attributes(audits_to(version)) + revision_with audit_class.reconstruct_attributes(audits_to(version)) end end # Find the oldest revision recorded prior to the date/time provided. def revision_at(date_or_time) audits = self.audits.up_until(date_or_time) - revision_with Audited.audit_class.reconstruct_attributes(audits) unless audits.empty? + revision_with audit_class.reconstruct_attributes(audits) unless audits.empty? end # List of attributes that are audited. @@ -177,7 +188,7 @@ def audited_attributes # Returns a list combined of record audits and associated audits. def own_and_associated_audits - Audited.audit_class.unscoped + audit_class.unscoped .where("(auditable_type = :type AND auditable_id = :id) OR (associated_type = :type AND associated_id = :id)", type: self.class.base_class.name, id: id) .order(created_at: :desc) @@ -206,7 +217,7 @@ def revision_with(attributes) revision.send :instance_variable_set, "@destroyed", false revision.send :instance_variable_set, "@_destroyed", false revision.send :instance_variable_set, "@marked_for_destruction", false - Audited.audit_class.assign_revision_attributes(revision, attributes) + audit_class.assign_revision_attributes(revision, attributes) # Remove any association proxies so that they will be recreated # and reference the correct object for this revision. The only way @@ -431,7 +442,7 @@ def enable_auditing # convenience wrapper around # @see Audit#as_user. def audit_as(user, &block) - Audited.audit_class.as_user(user, &block) + audit_class.as_user(user, &block) end def auditing_enabled diff --git a/lib/audited/rspec_matchers.rb b/lib/audited/rspec_matchers.rb index 1999068c..675b3f98 100644 --- a/lib/audited/rspec_matchers.rb +++ b/lib/audited/rspec_matchers.rb @@ -221,7 +221,7 @@ def reflection def association_exists? !reflection.nil? && reflection.macro == :has_many && - reflection.options[:class_name] == Audited.audit_class.name + reflection.options[:class_name] == model_class.audit_class.name end end end diff --git a/spec/audited/audit_spec.rb b/spec/audited/audit_spec.rb index ae47b7d0..75c9f6b9 100644 --- a/spec/audited/audit_spec.rb +++ b/spec/audited/audit_spec.rb @@ -70,7 +70,7 @@ class Models::ActiveRecord::CustomUserSubclass < Models::ActiveRecord::CustomUse end it "does not unserialize from binary columns" do - allow(Audited::YAMLIfTextColumnType).to receive(:text_column?).and_return(false) + allow_any_instance_of(Audited::YAMLIfTextColumnType).to receive(:text_column?).and_return(false) audit.audited_changes = {foo: "bar"} expect(audit.audited_changes).to eq "{:foo=>\"bar\"}" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 967b5b85..08025fcf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,6 +17,7 @@ Dir[SPEC_ROOT.join("support/*.rb")].sort.each { |f| require f } +ActiveRecord.use_yaml_unsafe_load = true RSpec.configure do |config| config.include AuditedSpecHelpers config.use_transactional_fixtures = false if Rails.version.start_with?("4.")