Auditing models with user_stamp
As part of a CMS I’m building I wanted the ability to audit user interaction with data, to prevent the situation where content is being created, deleted or edited with no record of who did what and when.
John Nunemaker recently released his User Stamp plug-in which stamps records with the ID of the users that created and last updated. The implementation is quite clever, as he’s managed to work around the usual hack of storing the current user ID in Thread.current to make it accessible to the model by using a Sweeper, as they have access to controllers (and hence the current_user method).
I’ve taken his code a bit further by creating a Log model which stores a polymorphic reference to the record affected, the event that occurred (e.g. ‘deleted’ or ‘updated’), the current user ID and the time it happened.
The Log model;
class Log < ActiveRecord::Base # Relationships belongs_to :user belongs_to :loggable, :polymorphic => :true # Validations validates_presence_of :loggable_type validates_presence_of :loggable_id validates_presence_of :action validates_presence_of :user end
The migration;
class CreateLogs < ActiveRecord::Migration def self.up create_table :logs do |t| t.string :loggable_type t.integer :loggable_id t.string :action t.references :user t.timestamps end add_index :logs, :loggable_id add_index :logs, :user_id end def self.down drop_table :logs end end
Additional callback methods added to UserStampSweeper;
def after_create(record) add_log(record, 'created') end def after_update(record) add_log(record, 'updated') end def after_destroy(record) add_log(record, 'delete') end private def add_log(record, action) Log.create( :loggable_type => record.class.to_s, :loggable_id => record.id, :action => action, :user => current_user ) end
Displaying the logs;
<% for log in @logs %> <tr> <td><%= link_to (h log.loggable.log_name), [:admin, log.loggable] %></td> <td><%= h log.loggable_type %></td> <td><%= h log.action %></td> <td><%= link_to (h log.user.display_name), [:admin, log.user] %></td> <td><%= log.created_at.to_s %></td> </tr> <% end %>
