我们最近开始在我们公司进行合规性推广,并且需要保留我们目前在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
一般情况下这可以很好,但是在使用添加到has_many的“magic”<association>_ids
方法时失败了:通过=&gt;关联。
例如:
# 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 :(得分:9)
我对最近的一个项目有类似的要求。我最终使用了acts_as_audited宝石,它对我们来说很有用。
在我的应用程序控制器中,我的行如下
audit RunWay,RunWayModel,OtherModelName
它可以处理所有的魔法,它还会记录所有变化以及制作它们的变化 - 它很漂亮。
希望有所帮助
答案 1 :(得分:6)
有关详细信息,请参阅this屏幕广播。看看最近在这里回答的similar question。
Vestal versions
插件是最活跃的插件,它只存储delta。属于不同模型的增量存储在一个表中。
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
答案 2 :(得分: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
答案 3 :(得分:0)
您还可以使用act_as_versioned http://github.com/technoweenie/acts_as_versioned之类的内容 它会对您的表记录进行版本化,并在每次更改时创建副本(例如在wiki中) 这比审核文件
更容易审核(在界面中显示差异等)