Recientemente comenzamos un esfuerzo de cumplimiento en nuestra empresa y estamos obligados a mantener un historial completo de cambios en nuestros datos que actualmente se gestionan en una aplicación de Rails. Se nos ha dado la autorización para simplemente insertar algo descriptivo para cada acción en un archivo de registro, que es un camino bastante discreto.¿Cómo crear un registro de auditoría completo en Rails para cada tabla?
Mi inclinación es hacer algo como esto en ApplicationController
:
around_filter :set_logger_username
def set_logger_username
Thread.current["username"] = current_user.login || "guest"
yield
Thread.current["username"] = nil
end
A continuación, cree un observador que se ve algo como esto:
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
y en general esto funciona gran, pero falla cuando se usa el método "mágico" <association>_ids
que está añadido a has_many: through => asociaciones.
Por ejemplo:
# 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
Esto va a terminar la activación de la after_create
cuando nuevos Runway
registros están asociados, pero no activarán la before_destroy
cuando se elimina un RunwayModel
.
Mi pregunta es ... ¿Hay alguna manera de hacerlo funcionar para que observe esos cambios (y/o potencialmente otros borrados)?
¿Existe una solución mejor que todavía sea relativamente discreta?
Como un rápido aparte, traté de agregar un 'before_remove (auditable)' al observador, que esperaba no hacer nada, y confirmé. Además, intentamos editar 'activerecord-2.3.5/lib/active_record/associations/association_collection.rb: 327' y cambiar 'delete' a 'destroy', lo que tuvo malas consecuencias. –