How to create a full Audit log in Rails for every table?
activerecord, observer-pattern, ruby, ruby-on-rails
Solution
I had a similar requirement on a recent project. I ended using the acts_as_audited gem, and it worked great for us.
In my application controller I have line like the following
audit RunWay,RunWayModel,OtherModelName
and it takes care of all the magic, it also keeps a log of all the changes that were made and who made them-- its pretty slick.
Hope it helps
Problem
We recently began a compliance push at our company and are required to keep a full history of changes to our data which is currently managed in a Rails application. We've been given the OK to simply push something descriptive for every action to a log file, which is a fairly unobtrusive way to go. My inclination is to do something like this in `ApplicationController`: ``` around_filter :set_logger_username def set_logger_username Thread.current["username"] = current_user.login || "guest" yield Thread.current["username"] = nil end ``` Then create an observer that looks something like this: ``` class AuditObserver < ActiveRecord::Observer observe ... #all models that need to be observed def after_create(auditable) AUDIT_LOG.info "[#{username}][ADD][#{auditable.class.name}][#{auditable.id}]:#{auditable.inspect}" end def before_update(auditable) AUDIT_LOG.info "[#{username}][MOD][#{auditable.class.name}][#{auditable.id}]:#{auditable.changed.inspect}" end def before_destroy(auditable) AUDIT_LOG.info "[#{username}][DEL][#{auditable.class.name}][#{auditable.id}]:#{auditable.inspect}" end def username (Thread.current['username'] || "UNKNOWN").ljust(30) end end ``` and in general this works great, but it fails when using the "magic" `<association>_ids` method that is tacked to has_many :through => associations. For instance: ``` # model class MyModel has_many :runway_models, :dependent => :destroy has_many :runways, :through => :runway_models end #controller class MyModelController < ApplicationController # ... # params => {:my_model => {:runways_ids => ['1', '2', '3', '5', '8']}} def update respond_to do |format| if @my_model.update_attributes(params[:my_model]) flash[:notice] = 'My Model was successfully updated.' format.html { redirect_to(@my_model) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @my_model.errors, :status => :unprocessable_entity } end end end # ... end ``` This will end up triggering the `after_create` when new `Runway` records are associated, but will not trigger the `before_destroy` when a `RunwayModel` is deleted. My question is... Is there a way to make it work so that it will observe those changes (and/or potentially other deletes)? Is there a better solution that is still relatively unobtrusive?