17

ActiveRecordモデルとActiveResourceモデルの両方を使用した巨大なプロジェクトがあります。これらのモデルを使用してユーザーアクティビティのログを実装し、モデル属性の変更をログに記録する必要があります(オブジェクトの状態などを保存します)。変更は、ユーザーまたはcronrakeタスクによって行うことができます。

また、日付、フィールドなどでデータを検索する可能性も必要です。

たとえば、最後のアクティビティで読み取り可能なメッセージを生成することもできます

  • ユーザーBobは、2011-08-12 08:12にパスワードを*に変更し、電子メールを**に変更します。
  • スタッフジェフは新しいパートナーを追加しました:2011-08-1208:13の会社名
  • 管理者ジャックが製品を削除しました:2011-09-1211:11の製品名
  • クライアントSamが新しいサービスを注文しました:2011-09-1211:12のサービス名

誰かがそのようなロギングを実装していますか?アイデア?アドバイス?

宝石を使用する必要がありますか、それともモデルを変更しないオブザーバーですべてのロジックを実行できますか?


私はgemが好きでしたhttps://github.com/airblade/paper_trail誰でも、activeresourceで動作させる方法を教えてもらえますか?

4

6 に答える 6

4

あなたは探している

https://github.com/collectiveidea/acts_as_audited

そのプラグインを使用しているオープンソースプロジェクトはほとんどありません。RedMineTheForemanだと思います。

編集:残念ながら、ActiveResourceではなくActiveRecordしか実行できません。

于 2011-05-13T08:45:10.943 に答える
4

ファイベル、私はちょうどこの質問を見たばかりで、賞金が切れる前に今晩変更を処理する時間がないので、ActiveRecordで動作し、ActiveResourceで動作するはずの監査コードを、おそらくいくつかの調整を加えて提供します(Iオフハンドを知るのに十分な頻度でAResを使用しないでください)。私たちが使用するコールバックがそこにあることは知っていますが、AResにActiveRecordのダーティ属性changes追跡があるかどうかはわかりません。

このコードは、すべてのモデル(監査ログモデルのCREATEおよび指定したその他の例外を除く)の各CREATE / UPDATE / DELETEを、変更をJSONとして保存してログに記録します。クリーンアップされたバックトレースも保存されるため、変更を加えたコードを特定できます(これにより、MVCの任意のポイント、およびrakeタスクとコンソールの使用状況がキャプチャされます)。

このコードは、コンソールの使用、rakeタスク、およびhttp要求に対して機能しますが、通常、現在のユーザーをログに記録するのは最後の1つだけです。(私が正しく思い出せば、これが置き換えられたActiveRecordオブザーバーはrakeタスクまたはコンソールでは機能しませんでした。)ああ、このコードはRails2.3アプリからのものです-私はいくつかのRails3アプリを持っていますが、この種は必要ありませんまだ彼らのための監査の。

この情報を適切に表示するコードはありませんが(問題を調査する必要がある場合にのみデータを掘り下げます)、変更はJSONとして保存されるため、かなり簡単なはずです。

まず、現在のユーザーをUser.currentに保存して、どこからでもアクセスできるようにしapp/models/user.rbます。

Class User < ActiveRecord::Base
  cattr_accessor :current
  ...
end

現在のユーザーは、次のようにリクエストごとにアプリケーションコントローラーに設定されます(同時実行の問題は発生しません)。

def current_user
  User.current = session[:user_id] ? User.find_by_id(session[:user_id]) : nil
end

User.current理にかなっている場合は、レーキタスクを設定できます。

次に、監査情報を格納するモデルを定義します。監査したくないモデルに合わせapp/models/audit_log_entry.rbてカスタマイズする必要があります。IgnoreClassesRegEx

# == Schema Information
#
# Table name: audit_log_entries
#
#  id         :integer         not null, primary key
#  class_name :string(255)
#  entity_id  :integer
#  user_id    :integer
#  action     :string(255)
#  data       :text
#  call_chain :text
#  created_at :datetime
#  updated_at :datetime
#

class AuditLogEntry < ActiveRecord::Base
  IgnoreClassesRegEx = /^ActiveRecord::Acts::Versioned|ActiveRecord.*::Session|Session|Sequence|SchemaMigration|CronRun|CronRunMessage|FontMetric$/
  belongs_to :user

  def entity (reload = false)
    @entity = nil if reload
    begin
      @entity ||= Kernel.const_get(class_name).find_by_id(entity_id)
    rescue
      nil
    end
  end

  def call_chain
    return if call_chain_before_type_cast.blank?
    if call_chain_before_type_cast.instance_of?(Array)
      call_chain_before_type_cast
    else
      JSON.parse(call_chain_before_type_cast)
    end
  end
  def data
    return if data_before_type_cast.blank?
    if data_before_type_cast.instance_of?(Hash)
      data_before_type_cast
    else
      JSON.parse(data_before_type_cast)
    end
  end

  def self.debug_entity(class_name, entity_id)
    require 'fastercsv'
    FasterCSV.generate do |csv|
      csv << %w[class_name entity_id date action first_name last_name data]
      find_all_by_class_name_and_entity_id(class_name, entity_id,
                                           :order => 'created_at').each do |a|
        csv << [a.class_name, a.entity_id, a.created_at, a.action, 
          (a.user && a.user.first_name), (a.user && a.user.last_name), a.data]
      end
    end
  end
end

ActiveRecord::Base次に、監査を機能させるためのメソッドをいくつか追加します。メソッドを確認し、必要に応じて変更する必要がありますaudit_log_clean_backtrace。(FWIW、lib/extensions/*.rb初期化子にロードされる既存のクラスに追加を追加します。)In lib/extensions/active_record.rb

class ActiveRecord::Base
  cattr_accessor :audit_log_backtrace_cleaner
  after_create  :audit_log_on_create
  before_update :save_audit_log_update_diff
  after_update  :audit_log_on_update
  after_destroy :audit_log_on_destroy
  def audit_log_on_create
    return if self.class.name =~ /AuditLogEntry/
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    audit_log_create 'CREATE', self, caller
  end
  def save_audit_log_update_diff
    @audit_log_update_diff = changes.reject{ |k,v| 'updated_at' == k }
  end
  def audit_log_on_update
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    return if @audit_log_update_diff.empty?
    audit_log_create 'UPDATE', @audit_log_update_diff, caller
  end
  def audit_log_on_destroy
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    audit_log_create 'DESTROY', self, caller
  end
  def audit_log_create (action, data, call_chain)
    AuditLogEntry.create :user       => User.current,
                         :action     => action,
                         :class_name => self.class.name,
                         :entity_id  => id,
                         :data       => data.to_json,
                         :call_chain => audit_log_clean_backtrace(call_chain).to_json
  end
  def audit_log_clean_backtrace (backtrace)
    if !ActiveRecord::Base.audit_log_backtrace_cleaner
      ActiveRecord::Base.audit_log_backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/rake\.rb/ }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/bin\/rake/ }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/(action_controller|active_(support|record)|hoptoad_notifier|phusion_passenger|rack|ruby|sass)\// }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_filter   { |line| line.gsub(RAILS_ROOT, '') }
    end
    ActiveRecord::Base.audit_log_backtrace_cleaner.clean backtrace
  end
end

最後に、これについて行ったテストを示します。もちろん、実際のテストアクションを変更する必要があります。test/integration/audit_log_test.rb

require File.dirname(__FILE__) + '/../test_helper'

class AuditLogTest < ActionController::IntegrationTest
  def setup
  end

  def test_audit_log
    u = users(:manager)
    log_in u
    a = Alert.first :order => 'id DESC'
    visit 'alerts/new'
    fill_in 'alert_note'
    click_button 'Send Alert'
    a = Alert.first :order => 'id DESC', :conditions => ['id > ?', a ? a.id : 0]
    ale = AuditLogEntry.first :conditions => {:class_name => 'Alert', :entity_id => a.id }
    assert_equal 'Alert',  ale.class_name
    assert_equal 'CREATE', ale.action
  end

private

  def log_in (user, password = 'test', initial_url = home_path)
    visit initial_url
    assert_contain 'I forgot my password'
    fill_in 'email',    :with => user.email
    fill_in 'password', :with => password
    click_button 'Log In'
  end

  def log_out
    visit logout_path
    assert_contain 'I forgot my password'
  end
end

そしてtest/unit/audit_log_entry_test.rb

# == Schema Information
#
# Table name: audit_log_entries
#
#  id         :integer         not null, primary key
#  class_name :string(255)
#  action     :string(255)
#  data       :text
#  user_id    :integer
#  created_at :datetime
#  updated_at :datetime
#  entity_id  :integer
#  call_chain :text
#

require File.dirname(__FILE__) + '/../test_helper'

class AuditLogEntryTest < ActiveSupport::TestCase
  test 'should handle create update and delete' do
    record = Alert.new :note => 'Test Alert'
    assert_difference 'Alert.count' do
      assert_difference 'AuditLogEntry.count' do
        record.save
        ale = AuditLogEntry.first :order => 'created_at DESC'
        assert ale
        assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
        assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
        assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
      end
    end
    assert_difference 'AuditLogEntry.count' do
      record.update_attribute 'note', 'Test Update'
      ale = AuditLogEntry.first :order => 'created_at DESC'
      expected_data = {'note' => ['Test Alert', 'Test Update']}
      assert ale
      assert_equal 'UPDATE', ale.action, 'AuditLogEntry.action should be UPDATE'
      assert_equal expected_data, ale.data
      assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
      assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
    end
    assert_difference 'AuditLogEntry.count' do
      record.destroy
      ale = AuditLogEntry.first :order => 'created_at DESC'
      assert ale
      assert_equal 'DESTROY', ale.action, 'AuditLogEntry.action should be CREATE'
      assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
      assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
      assert_nil Alert.find_by_id(record.id), 'Alert should be deleted'
    end
  end

  test 'should not log AuditLogEntry create entry and block on update and delete' do
    record = Alert.new :note => 'Test Alert'
    assert_difference 'Alert.count' do
      assert_difference 'AuditLogEntry.count' do
        record.save
      end
    end
    ale = AuditLogEntry.first :order => 'created_at DESC'
    assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
    assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
    assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
    assert_nil AuditLogEntry.first(:conditions => { :class_name => 'AuditLogEntry', :entity_id => ale.id })

    if ale.user_id.nil?
      u = User.first
    else
      u = User.first :conditions => ['id != ?', ale.user_id]
    end
    ale.user_id = u.id
    assert !ale.save

    assert !ale.destroy
  end
end
于 2011-05-29T03:29:21.823 に答える
3

https://github.com/collectiveidea/acts_as_audited

https://github.com/airblade/paper_trail

はどちらも優れたソリューションですActiveRecordが、多くがActiveRecordに抽出されているため、少なくとも読み取り専用のサポートについては、ActiveModelどちらかをサポートするように拡張するのが妥当である可能性があります。ActiveResourceGithubネットワークのグラフを調べてグーグルで調べたところ、そのようなソリューションの開発は進行中ではないようですが、最初から始めるよりも、これら2つのプラグインのいずれかの上に実装する方が簡単だと思います。 paper_trailより活発な開発が行われているようで、Rails 3.1のコミットもあるため、Railsの内部はより最新で、拡張も簡単かもしれませんが、それは直感です。どちらの内部にも精通していません。

于 2011-05-24T21:46:11.937 に答える
1

acts_as_audited gemは、適切に機能するはずです:
https ://github.com/collectiveidea/acts_as_audited

また、ActiveResourceを考慮する限り、他のアプリケーションのモデルにもなります。サーバー側でgemを使用でき、クライアント側で監査する必要はありません。ActiveResourceを使用するすべてのCRUD操作は、最終的にActiveRecord(サーバー側)でのCRUD操作に変換されます。

したがって、おそらく遠くからそれを見る必要があり、同じ解決策が両方の場合に適用されますが、場所は異なります。

于 2011-05-22T12:24:19.333 に答える
1

ユーザーアクティビティ(CRUD)を追跡するために、Loggerから継承するクラスを作成しました。現在、構築されたRORアプリケーションに使用できるユーザーを追跡するための小さなプラグインを作成する予定です。そのようなプラグインがあるかどうかはすでに確認しましたが、表示されませんでした。paper-trail、acts_as_audited、itslogなどの宝石はたくさんあると思いますが、プラグインを使用することを好みます。助言がありますか?ここにあなたを助けるかもしれないリンクがあります:http://robaldred.co.uk/2009/01/custom-log-files-for-your-ruby-on-rails-applications/comment-page-1/#comment- 342

素敵なコーディング

于 2011-06-29T08:02:09.463 に答える
0

このrailscastを見てください、多分それはあなたを助けることができます:通知

于 2011-05-26T10:28:42.083 に答える