21

我们最近开始在我们的公司推动合规性,并且需要保留对当前在 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_createRunway记录的时间,但不会触发删除before_destroya的时间。RunwayModel

我的问题是......有没有办法让它工作,以便它观察这些变化(和/或可能的其他删除)?
是否有更好的解决方案仍然相对不引人注目?

4

4 回答 4

11

我对最近的一个项目有类似的要求。我结束了使用acts_as_audited gem,它对我们非常有用。

在我的应用程序控制器中,我有如下行

audit RunWay,RunWayModel,OtherModelName

它负责所有的魔法,它还记录所有所做的更改以及谁做了这些更改——它非常漂亮。

希望能帮助到你

于 2010-03-04T16:59:22.750 回答
6

为此使用Vestal 版本插件

有关更多详细信息,请参阅屏幕截图。看看最近在这里回答的类似问题。

Vestal versionsplugin 是最活跃的插件,它只存储 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
于 2010-03-04T17:37:56.227 回答
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

于 2010-03-04T17:34:37.427 回答
0

你也可以使用类似acts_as_versioned http://github.com/technoweenie/acts_as_versioned
它版本你的表记录并在每次发生变化时创建一个副本(例如在wiki中)
这将更容易审计(在一个接口等)而不是日志文件

于 2010-03-04T16:58:55.007 回答