2010-03-04 2 views
20

Недавно мы начали действовать в нашей компании и должны вести полную историю изменений наших данных, которые в настоящее время управляются в приложении Rails. Нам дали ОК, чтобы просто направить что-то описательное для каждого действия в файл журнала, что является довольно ненавязчивым способом.Как создать полный журнал аудита в Rails для каждой таблицы?

Мое наклонения сделать что-то подобное в ApplicationController:

around_filter :set_logger_username 

def set_logger_username 
    Thread.current["username"] = current_user.login || "guest" 
    yield 
    Thread.current["username"] = nil 
end 

Затем создай наблюдатель, который выглядит примерно так:

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 

и в целом это работает большого, но сбой при использовании метода «magic» <association>_ids, который привязан к has_many: through => ассоциациям.

Например:

# 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 

Это в конечном итоге запуская after_create, когда новые Runway записи связаны, но не вызовет before_destroy когда RunwayModel удаляется.

Мой вопрос ... Есть ли способ заставить его работать, чтобы он соблюдал эти изменения (и/или потенциально другие удаляет)?
Есть ли лучшее решение, которое по-прежнему относительно ненавязчиво?

+0

как бы в стороне, я попытался добавить к наблюдателю 'before_remove (auditable)', который я ожидал ничего не делать и подтвердил. Кроме того, мы попытались отредактировать 'activerecord-2.3.5/lib/active_record/association/association_collection.rb: 327' и сменив« delete »на« destroy », что имело плохие последствия. –

ответ

0

Вы также могли бы использовать что-то вроде acts_as_versioned http://github.com/technoweenie/acts_as_versioned
It версии ваших записей таблицы и создает копию каждый раз, когда что-то меняется (например, в вики, например)
Это было бы проще провести аудит (различия показываются в интерфейс и т.д.), чем файл журнала

9

У меня было аналогичное требование для недавнего проекта. Я закончил использовать драгоценный камень acts_as_audited, и он отлично поработал для нас.

В моем контроллере приложения У меня есть строка, как в следующем

audit RunWay,RunWayModel,OtherModelName 

и берет на себя всю магию, он также ведет журнал всех изменений, которые были сделаны и которые сделали его довольно them-- скользкий.

Надеется, что это помогает

+3

Теперь act_as_audited стал [проверенным] (https://github.com/collectiveidea/audited). – pgericson

2

Добавлены эта обезьяна-патч к нашим 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 

Это падение производительности (!), Но удовлетворяет требование и учитывая тот факт, что этот destroy_all не получит называют часто, это работает для наших нужд - хотя я собираюсь проверить acts_as_versioned и acts_as_audited

+0

У меня есть сомнения в том, что handle_as_versioned и actions_as_audited обрабатывают has_many через ассоциации. Но это хорошо работает, и это не так уж сильно влияет на производительность. Чтобы быть уверенным, я сделал это изменение в ActiveRecord lib и выполнил его модульные тесты. Все прошло, за исключением трех тестов, которые проверяют количество запущенных SQL-запросов. В этих трех случаях был один дополнительный оператор «select» (выбор по идентификаторам ассоциации), и теперь инструкция «удалить» теперь выбирает вместо PK таблицы соединений. Обратите внимание: если вы также используете отношения HABTM, вам может понадобиться другой патч – Chris

6

Используйте Vestal versions plugin для этого:

Для получения более подробной информации обратитесь к this. Посмотрите на similar question.

Vestal versions плагин является самым активным плагином и хранит только дельту.Дельта, принадлежащая разным моделям, хранится в одной таблице.

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

Может ли пользователь и время, связанное с каждым изменением, автоматически? – Nitrodist

Смежные вопросы