如何在 Rails 中为每个表创建完整的审核日志?

发布于 2024-08-23 15:20:49 字数 2115 浏览 4 评论 0原文

我们最近开始在公司推行合规性推动,并要求保留当前在 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”时会失败" 附加到 has_many :through => 的 _ids 方法协会。

例如:

# 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

当关联新的 Runway 记录时,这最终会触发 after_create,但当 RunwayModel 时,不会触发 before_destroy 已删除。

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

We recently began a compliance push at our company and are required to keep a full history of changes to our data which is currently managed in a Rails application. We've been given the OK to simply push something descriptive for every action to a log file, which is a fairly unobtrusive way to go.

My inclination is to do something like this in ApplicationController:

around_filter :set_logger_username

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

Then create an observer that looks something like this:

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

and in general this works great, but it fails when using the "magic" <association>_ids method that is tacked to has_many :through => associations.

For instance:

# 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

This will end up triggering the after_create when new Runway records are associated, but will not trigger the before_destroy when a RunwayModel is deleted.

My question is...
Is there a way to make it work so that it will observe those changes (and/or potentially other deletes)?
Is there a better solution that is still relatively unobtrusive?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

身边 2024-08-30 15:20:49

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

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

audit RunWay,RunWayModel,OtherModelName

,它负责所有的魔法,它还保留所做的所有更改以及谁进行的更改的日志 - 它非常光滑。

希望有帮助

I had a similar requirement on a recent project. I ended using the acts_as_audited gem, and it worked great for us.

In my application controller I have line like the following

audit RunWay,RunWayModel,OtherModelName

and it takes care of all the magic, it also keeps a log of all the changes that were made and who made them-- its pretty slick.

Hope it helps

余生一个溪 2024-08-30 15:20:49

为此,请使用 Vestal 版本插件

请参阅 此屏幕截图了解更多详细信息。查看 类似最近在这里回答了问题

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

Use the Vestal versions plugin for this:

Refer to this screen cast for more details. Look at the similar question answered here recently.

Vestal versions plugin is the most active plugin and it only stores delta. The delta belonging to different models are stored in one table.

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
栀子花开つ 2024-08-30 15:20:49

将此猴子补丁添加到我们的 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

Added this monkey-patch to our 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

It is a performance hit(!), but satisfies the requirement and considering the fact that this destroy_all doesn't get called often, it works for our needs--though I am going to check out acts_as_versioned and acts_as_audited

一梦浮鱼 2024-08-30 15:20:49

您还可以使用类似acts_as_versioned http://github.com/technoweenie/acts_as_versioned
它对您的表记录进行版本控制,并在每次发生更改时创建一个副本(例如在 wiki 中)
这比日志文件更容易审核(显示界面中的差异等)

You could also use something like acts_as_versioned http://github.com/technoweenie/acts_as_versioned
It versions your table records and creates a copy every time something changes (like in a wiki for instance)
This would be easier to audit (show diffs in an interface etc) than a log file

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文