Auditor
Auditor is a Rails 3 plugin for auditing access to your ActiveRecord model objects. It allows you to declaratively specify what CRUD operations should be audited and store that audit data in the database. You can also specify what attributes of model objects should automatically be audited and which ones should be ignored.
To audit your model objects you must specify which operations should be audited and which model attributes should be tracked. This “specify what you want to collect” approach avoids being overwhelmed with data and makes you carefully consider what is most important to audit.
Installation
To use it with your Rails 3 project, add the following line to your Gemfile
gem 'auditor'
Auditor can also be installed as a Rails plugin
rails plugin install git://github.com/nearinfinity/auditor.git
Generate the migration and create the audits table
rails generate auditor:migration
rake db:migrate
Upgrading
You will need to run the upgrade migration if coming from a version earlier than 2.1.0
rails generate auditor:upgrade
rake db:migrate
Setup
Auditor needs to know who the current user is, but with no standard for doing so you’ll have to do a little work to set things up. You simply need to set your current user model object as the Auditor current user before any CRUD operations are performed. For example, in a Rails application you could add the following to your application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_current_user
private
def set_current_user
Auditor::User.current_user = @current_user
end
end
Examples
Auditor works very similarly to Joshua Clayton’s acts_as_auditable plugin. There are two audit calls in the example below. The first declares that create and update actions should be audited for the EditablePage model and the string returned by the passed block should be included as a custom message. The second audit call simply changes the custom message when auditing destroy (aka delete) actions.
class Page < ActiveRecord::Base
audit(:create, :update) { |model, user, action| "Page modified by #{user.display_name}" }
audit(:destroy) { |model, user, action| "#{user.display_name} deleted page #{model.id}" }
end
All audit data is stored in a table named Audits, which is automatically created for you when you run the migration included with the plugin. However, there’s a lot more recorded than just the custom message, including:
-
auditable_id - the primary key of the table belonging to the audited model object
-
auditable_type - the class type of the audited model object
-
owner_id - the primary key of the of the model that owns this audit record
-
owner_type - the class type of the owner model object
-
user_id - the primary key of the table belonging to the user being audited
-
user_type - the class type of the model object representing users in your application
-
action - a string indicating the action that was audited (create, update, destroy, or find)
-
audited_changes - a YAML string containing the before and after state of any model attributes that changed
-
comment - the custom message returned by any block passed to the audit call
-
version - an auditor-internal revision number for the audited model
-
created_at - the date and time the audit record was recorded
The audited_changes column automatically serializes the changes of any model attributes modified during the action. If there are only a few attributes you want to audit or a couple that you want to prevent from being audited, you can specify that in the audit call. For example
# Prevent SSN and passwords from being saved in the audit table
audit(:create, :destroy, :except => [:ssn, :password])
# Only audit edits to the title column when destroying/deleting
audit(:destroy, :only => :title)
# Associate the audit records with a related model, which becomes the owner
audit(:update, :on => :book)
# Associate the audit records with a related model, multiple levels up.
# Here, we're auditing a great-grandchild where :parent will be the owner. Order is important.
audit(:update, :on => [:grandchild, :child, :parent])
Make Auditing Important
There’s an alternate form of specifying your audit requirements that will cause the create, find, update, or destroy to fail if for some reason the audit record cannot be saved to the database. Instead of calling audit, call audit! instead.
class Page < ActiveRecord::Base
audit!(:create, :update) { |model, user, action| "Page modified by #{user.display_name}" }
audit!(:destroy) { |model, user, action| "#{user.display_name} deleted page #{model.id}" }
end
Auditable Versioning
Since auditor will keep a “diff” of all the changes applied to a model object, you can retrieve the state of any audited model object’s attributes at any point in time. For this to work, you have to specify auditing for all actions that modify the table, which is create, update, and destroy. Assuming those attributes have been declared with a call to audit or audit!, the following shows you how to use the revisions.
p = Page.create(:title => "Revision 1")
p.audits.last.attribute_snapshot
> {:title => "Revision 1"}
time = Time.now
p.author = "Jeff"
p.save
p.audits.last.attribute_snapshot
> {:title => "Revision 1", :author => "Jeff"}
p.attributes_at(time)
> {:title => "Revision 1"}
Integration
There may be some instances where you need to perform an action on your model object without Auditor recording the action. In those cases you can include the Auditor::Status module for help.
class PagesController < ApplicationController
include Auditor::Status
def update
page = Page.find(params[:id])
without_auditing { page.update_attributes(params[:page]) } # Auditor is disabled for the entire block
end
end
end
You can also force Auditor to audit any actions within a block as a specified user.
class PagesController < ApplicationController
include Auditor::Status
def update
page = Page.find(params[:id])
# Auditor will attribute update to 'another user'
audit_as(another_user) { page.update_attributes(params[:page]) }
end
end
end
License
Auditor is released under the MIT license.
Copyright © 2011 Near Infinity. www.nearinfinity.com