ruby - Sistema de auditoría Rails con ActiveResource y ActiveRecord
ruby-on-rails-3 logging (6)
La gema acts_as_audited debería funcionar bien para ti:
https://github.com/collectiveidea/acts_as_audited
Y en lo que se refiere a ActiveResource, también será un modelo en alguna otra aplicación. Puede usar la gema en el lado del servidor, y no necesita auditarla en el lado del cliente. Todas las operaciones CRUD que usan ActiveResource finalmente se traducirían a operaciones CRUD en ActiveRecord (en el lado del servidor).
Por lo tanto, probablemente tenga que mirarlo desde la distancia, y la misma solución se aplicaría en ambos casos, pero en diferentes lugares.
Tengo un gran proyecto con los modelos ActiveRecord y ActiveResource. Necesito implementar el registro de la actividad del usuario con estos modelos y también registrar los cambios de los atributos del modelo (guardar el estado del objeto o algo así). Los cambios pueden ser realizados por usuarios o por tareas cron rake.
También debo tener la posibilidad de buscar cualquier dato por fecha, cualquier campo ... etc.
Será bueno también generar mensajes legibles con la última actividad, por ejemplo
- El usuario Bob cambia su contraseña a * y envía un correo electrónico a ** en 2011-08-12 08:12
- Staff Jeff agregó nuevo socio: Nombre de la empresa en 2011-08-12 08:13
- Admin Jack ha eliminado el producto: Nombre del producto en 2011-09-12 11:11
- Cliente Sam ordenó un nuevo servicio: Nombre del servicio en 2011-09-12 11:12
¿Alguien implementa tal registro? Ideas? Consejos?
¿Debería usar gemas o puedo hacer toda la lógica con observadores que no cambian de modelo?
Me gustó gema https://github.com/airblade/paper_trail ¿alguien puede decir cómo puedo hacer que funcione con activeresource?
https://github.com/collectiveidea/acts_as_audited
y
https://github.com/airblade/paper_trail
son ambas excelentes soluciones para ActiveRecord
solamente, pero dado que gran parte de ActiveRecord
se ha extraído a ActiveModel
, es probable que sea razonable ampliarlo para que también sea compatible con ActiveResource
, al menos para soporte de solo lectura. Miré a través de los gráficos de la red de Github y busqué en Google y no parece haber ningún desarrollo en curso de dicha solución, sin embargo, espero que sea más fácil de implementar sobre uno de estos dos complementos que comenzar desde cero. paper_trail
parece estar en un desarrollo más activo y tiene algunos compromisos para Rails 3.1, por lo que puede estar más actualizado con los componentes internos de Rails y más fácil de extender, pero eso es solo un instinto: no estoy familiarizado con los aspectos internos de ninguno de los dos.
Echa un vistazo a esta transmisión de rieles, tal vez te pueda ayudar: Notificaciones
Estás buscando
https://github.com/collectiveidea/acts_as_audited
Pocos proyectos de código abierto usan ese complemento, creo que Red Mine y The Foreman .
Editar : lamentablemente solo puede hacer ActiveRecord, no ActiveResource.
Fivell, acabo de ver esta pregunta y no tengo tiempo para arreglar alteraciones esta noche antes de que caduque la recompensa, así que le daré mi código de auditoría que funciona con ActiveRecord y debería funcionar con ActiveResource, quizás con algunos ajustes (I no use ARes con la frecuencia suficiente como para saberlo). Sé que las devoluciones de llamada que usamos están ahí, pero no estoy seguro si ARes tiene el seguimiento de changes
atributos sucios de ActiveRecord.
Este código registra cada CREATE / UPDATE / DELETE en todos los modelos (excepto CREATE en el modelo de registro de auditoría y cualquier otra excepción que especifique) con los cambios almacenados como JSON. También se almacena una traza inversa limpia para que pueda determinar qué código hizo el cambio (esto captura cualquier punto en su MVC, así como las tareas de rake y el uso de la consola).
Este código funciona para el uso de la consola, las tareas de rake y las solicitudes http, aunque generalmente solo el último registra al usuario actual. (Si mal no recuerdo, el observador ActiveRecord que este reemplazó no funcionó en las tareas de rake o la consola.) Oh, este código proviene de una aplicación Rails 2.3. Tengo un par de aplicaciones de Rails 3, pero no he necesitado este tipo. de auditar para ellos todavía.
No tengo un código que cree una buena visualización de esta información (solo profundizamos en los datos cuando tenemos que analizar un problema), pero dado que los cambios se almacenan como JSON, debería ser bastante sencillo.
Primero, almacenamos el usuario actual en User.current para que esté accesible en todas partes, por lo que en app/models/user.rb
:
Class User < ActiveRecord::Base
cattr_accessor :current
...
end
El usuario actual se configura en el controlador de la aplicación para cada solicitud de ese modo (y no causa problemas de concurrencia):
def current_user
User.current = session[:user_id] ? User.find_by_id(session[:user_id]) : nil
end
Puede establecer User.current
en sus tareas de rake si tiene sentido hacerlo.
A continuación, definimos el modelo para almacenar la app/models/audit_log_entry.rb
información de auditoría app/models/audit_log_entry.rb
- usted querrá personalizar IgnoreClassesRegEx
para que se ajuste a cualquier modelo que no desee auditar:
# == Schema Information
#
# Table name: audit_log_entries
#
# id :integer not null, primary key
# class_name :string(255)
# entity_id :integer
# user_id :integer
# action :string(255)
# data :text
# call_chain :text
# created_at :datetime
# updated_at :datetime
#
class AuditLogEntry < ActiveRecord::Base
IgnoreClassesRegEx = /^ActiveRecord::Acts::Versioned|ActiveRecord.*::Session|Session|Sequence|SchemaMigration|CronRun|CronRunMessage|FontMetric$/
belongs_to :user
def entity (reload = false)
@entity = nil if reload
begin
@entity ||= Kernel.const_get(class_name).find_by_id(entity_id)
rescue
nil
end
end
def call_chain
return if call_chain_before_type_cast.blank?
if call_chain_before_type_cast.instance_of?(Array)
call_chain_before_type_cast
else
JSON.parse(call_chain_before_type_cast)
end
end
def data
return if data_before_type_cast.blank?
if data_before_type_cast.instance_of?(Hash)
data_before_type_cast
else
JSON.parse(data_before_type_cast)
end
end
def self.debug_entity(class_name, entity_id)
require ''fastercsv''
FasterCSV.generate do |csv|
csv << %w[class_name entity_id date action first_name last_name data]
find_all_by_class_name_and_entity_id(class_name, entity_id,
:order => ''created_at'').each do |a|
csv << [a.class_name, a.entity_id, a.created_at, a.action,
(a.user && a.user.first_name), (a.user && a.user.last_name), a.data]
end
end
end
end
A continuación, agregamos algunos métodos a ActiveRecord::Base
para que funcionen las auditorías. Deberá consultar el método audit_log_clean_backtrace
y modificarlo según sus necesidades. (FWIW, agregamos adiciones a las clases existentes en lib/extensions/*.rb
que se cargan en un inicializador). En lib/extensions/active_record.rb
:
class ActiveRecord::Base
cattr_accessor :audit_log_backtrace_cleaner
after_create :audit_log_on_create
before_update :save_audit_log_update_diff
after_update :audit_log_on_update
after_destroy :audit_log_on_destroy
def audit_log_on_create
return if self.class.name =~ /AuditLogEntry/
return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
audit_log_create ''CREATE'', self, caller
end
def save_audit_log_update_diff
@audit_log_update_diff = changes.reject{ |k,v| ''updated_at'' == k }
end
def audit_log_on_update
return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
return if @audit_log_update_diff.empty?
audit_log_create ''UPDATE'', @audit_log_update_diff, caller
end
def audit_log_on_destroy
return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
audit_log_create ''DESTROY'', self, caller
end
def audit_log_create (action, data, call_chain)
AuditLogEntry.create :user => User.current,
:action => action,
:class_name => self.class.name,
:entity_id => id,
:data => data.to_json,
:call_chain => audit_log_clean_backtrace(call_chain).to_json
end
def audit_log_clean_backtrace (backtrace)
if !ActiveRecord::Base.audit_log_backtrace_cleaner
ActiveRecord::Base.audit_log_backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ ///lib//rake/.rb/ }
ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ ///bin//rake/ }
ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ ///lib//(action_controller|active_(support|record)|hoptoad_notifier|phusion_passenger|rack|ruby|sass)/// }
ActiveRecord::Base.audit_log_backtrace_cleaner.add_filter { |line| line.gsub(RAILS_ROOT, '''') }
end
ActiveRecord::Base.audit_log_backtrace_cleaner.clean backtrace
end
end
Finalmente, aquí están las pruebas que tenemos sobre esto: por supuesto, deberá modificar las acciones de prueba reales. test/integration/audit_log_test.rb
require File.dirname(__FILE__) + ''/../test_helper''
class AuditLogTest < ActionController::IntegrationTest
def setup
end
def test_audit_log
u = users(:manager)
log_in u
a = Alert.first :order => ''id DESC''
visit ''alerts/new''
fill_in ''alert_note''
click_button ''Send Alert''
a = Alert.first :order => ''id DESC'', :conditions => [''id > ?'', a ? a.id : 0]
ale = AuditLogEntry.first :conditions => {:class_name => ''Alert'', :entity_id => a.id }
assert_equal ''Alert'', ale.class_name
assert_equal ''CREATE'', ale.action
end
private
def log_in (user, password = ''test'', initial_url = home_path)
visit initial_url
assert_contain ''I forgot my password''
fill_in ''email'', :with => user.email
fill_in ''password'', :with => password
click_button ''Log In''
end
def log_out
visit logout_path
assert_contain ''I forgot my password''
end
end
Y test/unit/audit_log_entry_test.rb
:
# == Schema Information
#
# Table name: audit_log_entries
#
# id :integer not null, primary key
# class_name :string(255)
# action :string(255)
# data :text
# user_id :integer
# created_at :datetime
# updated_at :datetime
# entity_id :integer
# call_chain :text
#
require File.dirname(__FILE__) + ''/../test_helper''
class AuditLogEntryTest < ActiveSupport::TestCase
test ''should handle create update and delete'' do
record = Alert.new :note => ''Test Alert''
assert_difference ''Alert.count'' do
assert_difference ''AuditLogEntry.count'' do
record.save
ale = AuditLogEntry.first :order => ''created_at DESC''
assert ale
assert_equal ''CREATE'', ale.action, ''AuditLogEntry.action should be CREATE''
assert_equal record.class.name, ale.class_name, ''AuditLogEntry.class_name should match record.class.name''
assert_equal record.id, ale.entity_id, ''AuditLogEntry.entity_id should match record.id''
end
end
assert_difference ''AuditLogEntry.count'' do
record.update_attribute ''note'', ''Test Update''
ale = AuditLogEntry.first :order => ''created_at DESC''
expected_data = {''note'' => [''Test Alert'', ''Test Update'']}
assert ale
assert_equal ''UPDATE'', ale.action, ''AuditLogEntry.action should be UPDATE''
assert_equal expected_data, ale.data
assert_equal record.class.name, ale.class_name, ''AuditLogEntry.class_name should match record.class.name''
assert_equal record.id, ale.entity_id, ''AuditLogEntry.entity_id should match record.id''
end
assert_difference ''AuditLogEntry.count'' do
record.destroy
ale = AuditLogEntry.first :order => ''created_at DESC''
assert ale
assert_equal ''DESTROY'', ale.action, ''AuditLogEntry.action should be CREATE''
assert_equal record.class.name, ale.class_name, ''AuditLogEntry.class_name should match record.class.name''
assert_equal record.id, ale.entity_id, ''AuditLogEntry.entity_id should match record.id''
assert_nil Alert.find_by_id(record.id), ''Alert should be deleted''
end
end
test ''should not log AuditLogEntry create entry and block on update and delete'' do
record = Alert.new :note => ''Test Alert''
assert_difference ''Alert.count'' do
assert_difference ''AuditLogEntry.count'' do
record.save
end
end
ale = AuditLogEntry.first :order => ''created_at DESC''
assert_equal ''CREATE'', ale.action, ''AuditLogEntry.action should be CREATE''
assert_equal record.class.name, ale.class_name, ''AuditLogEntry.class_name should match record.class.name''
assert_equal record.id, ale.entity_id, ''AuditLogEntry.entity_id should match record.id''
assert_nil AuditLogEntry.first(:conditions => { :class_name => ''AuditLogEntry'', :entity_id => ale.id })
if ale.user_id.nil?
u = User.first
else
u = User.first :conditions => [''id != ?'', ale.user_id]
end
ale.user_id = u.id
assert !ale.save
assert !ale.destroy
end
end
para rastrear la actividad del usuario (CRUD), he creado una clase que hereda de Logger, y ahora estoy planeando escribir un pequeño complemento para rastrear usuarios que pueda usar para cualquier aplicación ROR creada. Ya he comprobado si hay un complemento así, pero no lo vi. Supongo que hay muchas gemas como paper-trail, acts_as_audited o itslog, pero prefiero usar un plugin. ¿Alguna sugerencia? Aquí hay un enlace que podría ayudarlo: http://robaldred.co.uk/2009/01/custom-log-log-files-for-your-ruby-on-rails-applications/comment-page-1/#comment- 342
buena codificación