2010-03-04 50 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 

和一般工作原理偉大,但它在使用「魔術」<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記錄相關聯,但是當RunwayModel被刪除不會觸發before_destroy

我的問題是... 有沒有辦法使它工作,以便它會觀察到這些變化(和/或潛在的其他刪除)?
有沒有比較不顯眼的更好的解決方案?

+0

作爲一個快速拋開,我試圖給觀察者添加一個'before_remove(可審計)',我希望什麼都不做,並且確認。此外,我們嘗試編輯'activerecord-2.3.5/lib/active_record/associations/association_collection.rb:327'並將'delete'更改爲'destroy' - 這會產生不良後果。 – 2010-03-04 17:01:01

回答

0

您也可以使用類似acts_as_versioned http://github.com/technoweenie/acts_as_versioned
這版本的表中的記錄,並(在例如維基等)創建一個副本,每次有新的變化
這會更容易審覈(在接口等表演的diff )比日誌文件

9

我對最近的一個項目類似的要求。我結束了使用acts_as_audited寶石,它對我們很好。

在我的應用程序控制器我有一個像下面

audit RunWay,RunWayModel,OtherModelName 

,它需要的所有的魔法護線,它也保留日誌的經作出,而誰做them--其漂亮的種種變化光滑。

希望它可以幫助

+3

acts_as_audited現已變爲[已審覈](https://github.com/collectiveidea/audited)。 – pgericson 2016-04-15 15:16:07

2

添加了這個猴子補丁,我們​​

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

我有我的懷疑關於acts_as_versioned和acts_as_audited處理has_many是否通過關聯。但是,這種方式運行良好,並沒有太大的表現。可以肯定的是,我在ActiveRecord庫中做了這個改變,並運行了它的單元測試。除了三個檢查運行的SQL查詢的數量的測試之外,一切都通過了。在這三種情況下,有一個額外的「select」語句(由關聯Ids選擇),而「delete」語句現在由連接表的PK選擇。請注意,如果您還使用HABTM關係,則可能需要另一個補丁 – Chris 2010-03-04 17:57:02

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 2010-11-08 15:29:43