Why?
Auditing is one of those things that most businesses really should have built into every one of their IT systems but it's often seen as a nice-to-have that you will-never-have. Yet in every business I've worked at that has a web app you inevitably hit a problem where everyone wished there had been a trail of what had been done. As soon as you have any other person than the owner updating a record, you really should invest a little in auditing your data.
Let's talk about doing a little auditing.

Limiting Scope
It's important to note that this solution is not for everything in your system though there's no reason you couldn't easily extend this to do so. There are other solutions out there that are built to do auditing for every ActiveRecord class but I'm hesitant to jump into a turn-key solution by someone else for something as simple as Auditing.
Also important is that this is not a 'scalable' solution. It will suffice for our use case but I'd never want to count on this being our forever-solution.
I feel like this solution is in the ethos of Rails by keeping control inside your application rather than offloading it to your database. For that reason it should be easy to find and reason about by anyone on your development team.
SQL & Message Buses Can Do It!
If you're reading this you might've read articles that talk about using database triggers to create an audit log. I think the database solution falls somewhere between a real, scalable solution and what I'm presenting here. The reason I don't like database magic in Rails apps is because it's often out of sight and out of mind for developers - especially in a small team. Personally I'd always skip auditing by database triggers and move straight to a message bus type of solution. The other benefits to using a shared bus to transfer messages (eg writing workers to email, real time updates, etc) and the small leap in complexity to use a bus makes the trigger based solution seem less appealing to me. Having written a couple of auditing solutions using RabbitMQ, Ruby and Postgres I can say I'd always tend toward message buses if you have the technical team capable of managing it.
Solution Time
Given this blog post definitely took longer to write than the solution lets just get it out there!
There are 3 pieces to the puzzle:
- The logic that does the auditing
- The audit class itself
- The auditable objects
The Logic
This is a very simple module you mixin
module Auditable
extend ActiveSupport::Concern
class SystemUser < User
def id
0
end
end
included do
attr_accessor :updated_by
has_many :audits, as: :auditable
# One concern here is the ordering of callbacks ...
after_save :create_audit
end
private
def normalized_user
updated_by || SystemUser.new
end
def create_audit
Audit.create!(auditable: self, user: normalized_user)
end
end
The Audit Class
class Audit < ActiveRecord::Base
belongs_to :auditable, polymorphic: true
belongs_to :user
validates :auditable, presence: true
validates :user, presence: true
end
The Auditable Classes
class TravelDirection < ActiveRecord::Base
include Auditable
end
Seriously Folks, That's All
You'll notice the caveat in the Auditable
module about callback order. If you're a Rails aficionado then you know that callbacks just happen in the order they've been specified which might be a concern given we're adding it at module inclusion time. Now if you're writing nice callbacks then this shouldn't be a problem; why mutate a saved object and save again?! We have a pretty reasonable app in this regard as we're not doing anything much out of band, certainly someone writing such a callback would be slapped on the wrist. Auditing after the object you're auditing is saved should be safe. If not then it's trivial to modify the logic to give the developer more control over it.
In the end I think this took about 15 minutes to write and manually test. It was a spike but unit tests would be trivial to write and this solution will work for us for the foreseeable future.
Hopefully this helps someone, even if you're just writing a little audit trail for your tiny new business… which as I explained you most certainly should!

Lifts heavy things for fun, voracious learner, loves ice cream too much - if there is such a thing - even after losing 230lbs. Moved to Colorado via an E-3 visa in 2014 with over 15 years experience in software development in areas such as Finance, Agriculture, Retail and Social Networks.