Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define audit_class per model #641

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Define audit_class per model
  • Loading branch information
kuldeepaggarwal committed Nov 29, 2022
commit 7d9b0d6903311ab5b84bb7e78203a9101a836fd9
43 changes: 26 additions & 17 deletions lib/audited/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) }
Expand Down
29 changes: 20 additions & 9 deletions lib/audited/auditor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
#
Expand Down Expand Up @@ -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)
Expand All @@ -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 }
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/audited/rspec_matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/audited/audit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down