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