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

Allow custom Audit classes #319

Merged
merged 1 commit into from
Mar 28, 2017
Merged
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
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Audited.current_user_method = :authenticated_user
Outside of a request, Audited can still record the user with the `as_user` method:

```ruby
Audited::Audit.as_user(User.find(1)) do
Audited.audit_class.as_user(User.find(1)) do
post.update_attribute!(title: "Hello, world!")
end
post.audits.last.user # => #<User id: 1>
Expand Down Expand Up @@ -255,6 +255,26 @@ To disable auditing on an entire model:
User.auditing_enabled = false
```

### Custom `Audit` model

If you want to extend or modify the audit model, create a new class that
inherits from `Audited::Audit`:
```ruby
class CustomAudit < Audited::Audit
def some_custom_behavior
"Hiya!"
end
end
```
Then set it in an initializer:
```ruby
# config/initializers/audited.rb

Audited.config do |config|
config.audit_class = CustomAudit
end
```

## Gotchas

### Using attr_protected with Rails 4.x
Expand Down
10 changes: 6 additions & 4 deletions lib/audited.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

module Audited
class << self
attr_accessor :ignored_attributes, :current_user_method
attr_accessor :ignored_attributes, :current_user_method, :audit_class

# Deprecate audit_class accessors in preperation of their removal
def audit_class
Audited::Audit
@audit_class || Audit
end
deprecate audit_class: "Audited.audit_class is now always Audited::Audit. This method will be removed."

def store
Thread.current[:audited_store] ||= {}
end

def config
yield(self)
end
end

@ignored_attributes = %w(lock_version created_at updated_at created_on updated_on)
Expand Down
4 changes: 2 additions & 2 deletions lib/audited/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ module Audited
class YAMLIfTextColumnType
class << self
def load(obj)
if Audited::Audit.columns_hash["audited_changes"].sql_type == "text"
if Audited.audit_class.columns_hash["audited_changes"].sql_type == "text"
ActiveRecord::Coders::YAMLColumn.new(Object).load(obj)
else
obj
end
end

def dump(obj)
if Audited::Audit.columns_hash["audited_changes"].sql_type == "text"
if Audited.audit_class.columns_hash["audited_changes"].sql_type == "text"
ActiveRecord::Coders::YAMLColumn.new(Object).dump(obj)
else
obj
Expand Down
14 changes: 7 additions & 7 deletions lib/audited/auditor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def audited(options = {})

attr_accessor :audit_comment

has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audit.name
Audit.audited_class_names << to_s
has_many :audits, -> { order(version: :asc) }, as: :auditable, class_name: Audited.audit_class.name
Audited.audit_class.audited_class_names << to_s

on = Array(options[:on])
after_create :audit_create if on.empty? || on.include?(:create)
Expand All @@ -75,7 +75,7 @@ def audited(options = {})
end

def has_associated_audits
has_many :associated_audits, as: :associated, class_name: Audit.name
has_many :associated_audits, as: :associated, class_name: Audited.audit_class.name
end

def default_ignored_attributes
Expand Down Expand Up @@ -118,13 +118,13 @@ def revisions(from_version = 1)

# Get a specific revision specified by the version number, or +:previous+
def revision(version)
revision_with Audit.reconstruct_attributes(audits_to(version))
revision_with Audited.audit_class.reconstruct_attributes(audits_to(version))
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 Audit.reconstruct_attributes(audits) unless audits.empty?
revision_with Audited.audit_class.reconstruct_attributes(audits) unless audits.empty?
end

# List of attributes that are audited.
Expand Down Expand Up @@ -152,7 +152,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
Audit.assign_revision_attributes(revision, attributes)
Audited.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 @@ -298,7 +298,7 @@ def enable_auditing
# convenience wrapper around
# @see Audit#as_user.
def audit_as(user, &block)
Audit.as_user(user, &block)
Audited.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 @@ -170,7 +170,7 @@ def reflection
def association_exists?
!reflection.nil? &&
reflection.macro == :has_many &&
reflection.options[:class_name] == Audit.name
reflection.options[:class_name] == Audited.audit_class.name
end
end
end
Expand Down
48 changes: 48 additions & 0 deletions spec/audited/audit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,54 @@
describe Audited::Audit do
let(:user) { Models::ActiveRecord::User.new name: "Testing" }

describe "audit class" do
around(:example) do |example|
original_audit_class = Audited.audit_class

class CustomAudit < Audited::Audit
def custom_method
"I'm custom!"
end
end

class TempModel < ::ActiveRecord::Base
self.table_name = :companies
end

example.run

Audited.config { |config| config.audit_class = original_audit_class }
Audited::Audit.audited_class_names.delete("TempModel")
Object.send(:remove_const, :TempModel)
Object.send(:remove_const, :CustomAudit)
end

context "when a custom audit class is configured" do
it "should be used in place of #{described_class}" do
Audited.config { |config| config.audit_class = CustomAudit }
TempModel.audited

record = TempModel.create

audit = record.audits.first
expect(audit).to be_a CustomAudit
expect(audit.custom_method).to eq "I'm custom!"
end
end

context "when a custom audit class is not configured" do
it "should default to #{described_class}" do
TempModel.audited

record = TempModel.create

audit = record.audits.first
expect(audit).to be_a Audited::Audit
expect(audit.respond_to?(:custom_method)).to be false
end
end
end

describe "user=" do

it "should be able to set the user to a model object" do
Expand Down