The Limitation of PaperTrail
PaperTrail is fantastic, but in every app I’ve worked on, there’s at least two classes of users: Admin
and User
. Now, admins can change things and in some applications, users can, too. In business environments, different user classes may be able to edit your models. Any financial clients or ones with business sensitive information will want to keep an audit trail to see who’s updating what across the app. Out of the box, PaperTrail doesn’t support keeping track of multiple types of users. Good for us that PaperTrail’s provided us APIs to alter its behavior.
Intro to Whodunnit
By default, PaperTrail allows you to track which user edited a certain object with a few lines of configuration.
All you need is a current_user
method for a given controller, and then to add the following:
class ApplicationController
before_filter :set_paper_trail_whodunnit
end
Note, ApplicationController
can also be AdminController
or whatever other top level controller all editing behavior is done under. So long as all other controllers that handle Create, Update, Delete functionality inherit from this controller or have their own before_filter :set_paper_trail_whodunnit
, you’re good to go.
Going forward, this post assumes you’ve got this filter set correctly.
What It Does
class PaperTrail::Version < ActiveRecord::Base {
:created_at => :datetime,
:event => :string,
:id => :integer,
:item_id => :integer,
:item_type => :string,
:object => :text,
:object_changes => :text,
:whodunnit => :string,
}
Looking at the above, you’ll see the :whodunnit
field. Even though it’s a string, in the default configuration it stores an integer, which is the id of the editing person. This is set when an object is editing through a controller action.
Extending to Polymorphic Behavior
What needs to happen to get this to work with multiple user classes? If you haven’t yet, think about what happens when Admin.find(1)
and Lackey.find(1)
both edit the same object. :whodunnit
is set to "1"
in both cases. That’s correct, but is only half of what’s needed.
Migration
Rails standard convention for polymorphism is to use *_type
as the field name to specify which class to look up. In our case, just create the following migration and you’re good to go:
rails g migration add_whodunnit_type to versions whodunnit_type:string
The migration should have the following:
def change
add_column :versions, :whodunnit_type, :string
end
rake db:migrate
, and we’re ready for the next step.
Controller Changes
PaperTrail provides a helpful method for what we need: info_for_PaperTrail
. Use this within the controller to pass extra info to the creation of a PaperTrail::Version
object. Add this:
class ApplicationController < ActionController::Base
def info_for_paper_trail
{whodunnit_type: @current_user.class.name}
end
end
This passes in a new attribute that will be saved on each Version
with what’s needed to lookup the specific user that did it in a polymorphic way.
Adding to PaperTrail
The final part to make this developer friendly is to add an easy way to load up the responsible party. Ideally, Version#whodunnit
would be Version.whodunnit_id
. Since it’s not, I defined Version#perpetrator
.
#initializers/paper_trail.rb
PaperTrail::Version.class_eval do
def perpetrator
whodunnit_type.constantize.find(whodunnit.to_i) if (!!whodunnit && !!whodunnit_type)
end
end
This will take the class name, constantize it, and then return the perpetrator. Quick, easy and does what’s needed to get you up and running with polymorphic whodunnit on PaperTrail.
Wrapping Up
PaperTrail’s versioning and whodunnit to track auditing is fantastic. However, it falls apart if you have multiple user classes, such as Admin and User. Read on to keep your auditing trail clean and exact. With the above changes you can now track everyone across your app and when clients or users need to see changes, you can respond and display with 100% confidence the correct perpetrator of a given update on your models.