2010-03-04 11 views
20

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?

+0

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. –

Respuesta

0

También podría usar algo como acts_as_versioned http://github.com/technoweenie/acts_as_versioned
versiones It sus registros de la tabla y crea una copia cambia cada vez que algo (como en un wiki, por ejemplo)
Esto sería más fácil para auditar (Mostrar diferenciaciones en una interfaz, etc.) que un archivo de registro

9

Tenía un requisito similar en un proyecto reciente. Terminé usando la gema acts_as_audited, y funcionó muy bien para nosotros.

En mi controlador de aplicación tengo línea como la siguiente

audit RunWay,RunWayModel,OtherModelName 

y que se encarga de toda la magia, sino que también mantiene un registro de todos los cambios que se hicieron y que hicieron ellos-- es bastante mancha.

creo que sirve

+3

act_as_audited se ha convertido en [auditado] (https://github.com/collectiveidea/audited). – pgericson

2

añadido este mono-parche para nuestra lib/core_extensions.rb

ActiveRecord::Associations::HasManyThroughAssociation.class_eval do 
    def delete_records(records) 
    klass = @reflection.through_reflection.klass 
    records.each do |associate| 
     klass.destroy_all(construct_join_attributes(associate)) 
    end 
    end 
end 

Es un impacto en el rendimiento (!), Pero satisface el requisito y teniendo en cuenta el hecho de que este destroy_all no recibe llama a menudo, funciona para nuestras necesidades - a pesar de que voy a revisar acts_as_versioned y acts_as_audited

+0

Tengo mis dudas sobre si el manejador act_as_versioned y acts_as_audited tiene muchas asociaciones. Pero, esto funciona bien, y no tiene mucho impacto en el rendimiento. Solo para estar seguro, hice este cambio en la lib ActiveRecord y ejecuté sus pruebas unitarias. Todo pasó con la excepción de tres pruebas que verifican el número de consultas SQL que se ejecutan. En esos tres casos, había una declaración extra "select" (seleccionando por la asociación Ids), y la declaración "delete" ahora selecciona por PK de la tabla join en su lugar. Tenga en cuenta que si también está utilizando relaciones HABTM, es posible que necesite otro parche – Chris

6

uso del Vestal versions plugin para esto:

Consulte el elenco de pantalla this para obtener más detalles. Mira el similar question aquí respondido recientemente.

Vestal versions plugin es el plugin más activo y solo almacena delta.El delta perteneciente a diferentes modelos se almacena en una tabla.

class User < ActiveRecord::Base 
    versioned 
end 

# following lines of code is from the readme  
>> u = User.create(:first_name => "Steve", :last_name => "Richert") 
=> #<User first_name: "Steve", last_name: "Richert"> 
>> u.version 
=> 1 
>> u.update_attribute(:first_name, "Stephen") 
=> true 
>> u.name 
=> "Stephen Richert" 
>> u.version 
=> 2 
>> u.revert_to(10.seconds.ago) 
=> 1 
>> u.name 
=> "Steve Richert" 
>> u.version 
=> 1 
>> u.save 
=> true 
>> u.version 
=> 3 
+0

¿Puede haber un usuario y una hora asociados con cada cambio de forma automática? – Nitrodist

Cuestiones relacionadas