Rails のシンプルな証跡管理ライブラリ auditable

https://github.com/harleyttd/auditable

  • ちいさくてシンプル(かわいい)
  • メソッド(属性)であれば、変更を記録できる(汎用的)
  • ポリモーフィック関連とシリアライズしたものをカラムに入れるみたいなつくりで、トリッキーなことをあまりしていない(よめる)

あたらしめで、ちょいちょい更新されてるかんじ。

証跡管理は要件でブレがおおきいのかなー?という感じがするので、オシゴトでこれをそのまま使えるのかは知らない。仲間内でつかうようなものになら、気軽に利用してみてもいい気がする。

https://github.com/harleyttd/auditable/commit/d3c78b047998c83419cab6207d94f003caccc47c#README.md の時点の README の雰囲気訳。


Ruby toolbox のバージョニングカテゴリ証跡カテゴリにはたくさんの gem があるが、それぞれに様々な問題がある(これを書いている今現在も)。

  • ほとんどが古く、Rails 3.2.2 では動かない。人気のライブラリのいくつか、たとえば、papertrail と vestal_versions はたくさんの issue と pull request があげられているが、どれも処理されたりマージされたりしていない。こういう gem ライブラリのくさるほどある fork を見ると、みんなはそれぞれで gem を改善したもので良いと思ってるようだ。いい感じのものもあるのだが、自分の見たものだと、やたらとハックが多用されてたりした(最近の Rails の変更に対応するため)。そのなかで人気のあるものを試したけど、かんたんな、自分のやりたいこともちゃんとできない。
  • この手の gem は、ときとともに進化して、だんだん(すごく)扱いにくくなる
  • たいてい、データベースのカラムしかサポートしていなくて、たとえば、メソッドとか関連で動かない(か、微妙に動かない)。papertrail は has_one には対応しているが、作者はそれ以上に対応するのは簡単じゃないと言っている
  • シンプルで軽いのが欲しかった


上記のカテゴリの gem の多くはよくできてる。自分は、かんたんにモデルのアトリビュートとかメソッドの変更を扱えるような、もう完全にシンプルな gem を目標にした。そう、メソッドもですよ、アトリビュートだけじゃなくて。これが自分のライブラリのアプローチの特徴的なところです。

もし、込み入った情報、たとえば関連するレコードとかの変更を記録したいなら、関連するレコードを表現するような値を返すメソッド定義だけして、あとは、auditable にその値の変更を記録させておけばいい。

基本原理としては、

  • デフォルトでは、どのカラムについても、記録してほしくない。指定したアトリビュートとかメソッドだけでいい
  • ややこしい関連は扱いたくない。かわりにメソッドをつかう
  • データベースのカラムだけでなく、アトリビュートやメソッドの値を追いかけることに興味がある
  • シンプルで、ActiveRecord::Dirty#changes に似たものが欲しかったが、永続的に保存したい。下記の Auditable::Auditing#audited_changes の使い方を見てほしい。

導入

アプリケーションの Gemfile に以下の行を追加する。

gem 'auditable'

で、以下を実行。

$ bundle

あるいは gem コマンドから自分でインストールする。

$ gem install auditable

使い方

まず、audits テーブルをデータベースに追加する。

rails generate auditable:migration
rake db:migrate

そして、モデルで証跡管理したいメソッドのリストを audit メソッドに渡す。

class Survey
  has_many :questions
  attr_accessor :current_page

  audit :title, :current_page, :question_ids
end

デモ

テストコードにあるテストモデルでデモしたいと思います。監査対象のモデルを、たぶん、コンソールで試したいかと思うので。
詳細については、spec ディレクトリに含まれているテストケースを見てもらったほうがいいと思う。

$ bundle console
>> require(File.expand_path "../spec/spec_helper", __FILE__)
=> true

>> s = Survey.create :title => "demo"
=> #<Survey id: 1, title: "demo">

>> Survey.audited_attributes
=> [:title, :current_page]

>> s.audited_changes
=> {"title"=>[nil, "demo"]}

>> s.update_attributes(:title => "new title", :current_page => 2)
=> true

>> s.audited_changes
=> {"title"=>["demo", "new title"], "current_page"=>[nil, 2]}

>> s.update_attributes(:current_page => 3, :action => "modified", :changed_by => User.create(:name => "someone"))
=> true

>> s.audited_changes
=> {"current_page"=>[2, 3]}

>> s.audits.last
=> #<Auditable::Audit id: 3, auditable_id: 1, auditable_type: "Survey", user_id: 1, user_type: "User", modifications: {"title"=>"new title", "current_page"=>3}, action: "modified", created_at: ...>

>> s.audit_tag_with("something memorable")
   # 最新の audit にタグをつけた。それでは、s を変更してみる
   # ...
   # もし、s に何か変更を加えたとしても

>> s.audited_changes(:tag => "something memorable")
   # 上でタグつけたバージョンに対する変更が返ってくる(タグをつけたバージョンとの差分が見られる)
   # ※  s.audited_changes は、1つ前の audit との変更が返ってくる。
   # ほかのフィルターもあって、たとえば、s.audited_changes(:changed_by => some_user, :audit_action => "modified") # (これで以前の some_user の audit との変更が見られる)
   # ※ これは引数で指定したもの(:tag とか :changed_by)にマッチした audit の一番直近のものを使う。

仕組み

Audit モデル

上で見た、Audit モデルのマイグレーションファイルをこんな感じで用意する感じになる:

class CreateAudits < ActiveRecord::Migration
  def change
    create_table :audits do |t|
      t.belongs_to :auditable, :polymorphic => true
      t.belongs_to :user, :polymorphic => true
      t.text :modifications
      t.string :action
      t.timestamps
    end
  end
end

推察できるとおもうけど、audits.modifications は、監査(証跡)対象の属性のキーと値の表現をシリアライズしたものになる。

誰が、どのアクションで変更したのか?

誰がそのレコードを変更したのかとっておきたいならば、こんなかんじで changed_by 属性にそれを入れてあげればいい:

# `attr_accessor: changed_by` は、gem で Survey クラスで定義される
>> @survey.update_attributes(:changed_by => current_user, # あと他に attributes があればそれも)
# すると @surveys.audits.last.user は current_user がはいってる
# もちろん、changed_by に値を入れて save でも動く。

actioncreate または update だけ、レコードの操作に対応して入るが、それを仮想的な属性で上書きでき、change_action でよべる。(change_actionaudit_action に変更されてる気がする)。

>> @survey.changed_action = "add page"
>> @survey.update_attribute :page_count, 2

以上が、自分のできる README ドリブンなアプローチでした。またきます。

TODO

  • API を改善する(まだ扱いづらい) -- 良い書き方があったらください
  • 提案とフィードバックをもらう
  • README の更新

たとえば、今、変更はシリアライズして audits.modifications カラムに入れているが、複数の監査(証跡)対象をそれぞれ保存したいときはどうしたものか。こういう感じの書き方でサポートしようと思ってる:

# audits.trivial_changes カラム(かんたんに追加できる) にスナップショットを入れておく。
audit :modifications => [:method_1, :method_2], :trivial_changes => [:method_3, :method_4, :method_5]

Contributing

提案を歓迎します。Github で issue をあげてください。

あと、pull require も歓迎です!

  1. fork する
  2. feature ブランチをつくる (git checkout -b my-new-feature)
  3. 変更をコミットする(git commit -am 'Added some feature')
  4. ブランチに push(git push origin my-new-feature)
  5. Pull Request する


詳細はこちら: https://github.com/harleyttd/auditable