読者です 読者をやめる 読者になる 読者になる

auditedを使ってモデルの変更を監視しよう(適用編)

前回からの続きです。

監査証跡を取るには、モデルに対して audited を呼び出してあげれば良いです。こんな感じです。

class Company < ActiveRecord::Base
   audited
end

これでどのように監査証跡が取られるか、rails consoleの実行結果で見てみます。

tsubame.local{miyohide}% rails c
Loading development environment (Rails 3.2.2)
irb(main):001:0> Company.create(:name => "aaa")
   (0.1ms)  begin transaction
  SQL (20.4ms)  INSERT INTO "companies" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 27 May 2012 10:18:10 UTC +00:00], ["name", "aaa"], ["updated_at", Sun, 27 May 2012 10:18:10 UTC +00:00]]
   (0.2ms)  SELECT MAX("audits"."version") AS max_id FROM "audits" WHERE "audits"."auditable_id" = 2 AND "audits"."auditable_type" = 'Company'
  SQL (0.6ms)  INSERT INTO "audits" ("action", "associated_id", "associated_type", "auditable_id", "auditable_type", "audited_changes", "comment", "created_at", "remote_address", "user_id", "user_type", "username", "version") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["action", "create"], ["associated_id", nil], ["associated_type", nil], ["auditable_id", 2], ["auditable_type", "Company"], ["audited_changes", "---\nname: aaa\n"], ["comment", nil], ["created_at", Sun, 27 May 2012 10:18:10 UTC +00:00], ["remote_address", nil], ["user_id", nil], ["user_type", nil], ["username", nil], ["version", 1]]
   (1.1ms)  commit transaction
=> #<Company id: 2, name: "aaa", created_at: "2012-05-27 10:18:10", updated_at: "2012-05-27 10:18:10">
irb(main):002:0> 

companiesテーブルに対するinsertを行った後、auditsテーブルに対してselect処理とinsert処理を行っています。

モデルに対して audited の一行を書くことで、関連も張られます。自身に関連する監査証跡を見るには、audits を呼び出してあげればOK。

irb(main):002:0> c = Company.find(2)  # 先ほど作ったデータのidが2なので、find(2)で検索。
  Company Load (3.6ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = ? LIMIT 1  [["id", 2]]
=> #<Company id: 2, name: "aaa", created_at: "2012-05-27 10:18:10", updated_at: "2012-05-27 10:18:10">
irb(main):003:0> c.audits
  Audited::Adapters::ActiveRecord::Audit Load (0.5ms)  SELECT "audits".* FROM "audits" WHERE "audits"."auditable_id" = 2 AND "audits"."auditable_type" = 'Company' ORDER BY version
=> [#<Audited::Adapters::ActiveRecord::Audit id: 1, auditable_id: 2, auditable_type: "Company", associated_id: nil, associated_type: nil, user_id: nil, user_type: nil, username: nil, action: "create", audited_changes: {"name"=>"aaa"}, version: 1, comment: nil, remote_address: nil, created_at: "2012-05-27 10:18:10">]
irb(main):004:0> 

データの登録だけでなく、更新や削除もOK。

irb(main):005:0> c.update_attributes(:name => "bbb")
   (0.1ms)  begin transaction
   (0.3ms)  SELECT MAX("audits"."version") AS max_id FROM "audits" WHERE "audits"."auditable_id" = 2 AND "audits"."auditable_type" = 'Company'
  SQL (2.5ms)  INSERT INTO "audits" ("action", "associated_id", "associated_type", "auditable_id", "auditable_type", "audited_changes", "comment", "created_at", "remote_address", "user_id", "user_type", "username", "version") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["action", "update"], ["associated_id", nil], ["associated_type", nil], ["auditable_id", 2], ["auditable_type", "Company"], ["audited_changes", "---\nname:\n- aaa\n- bbb\n"], ["comment", nil], ["created_at", Sun, 27 May 2012 10:41:57 UTC +00:00], ["remote_address", nil], ["user_id", nil], ["user_type", nil], ["username", nil], ["version", 2]]
   (0.2ms)  UPDATE "companies" SET "name" = 'bbb', "updated_at" = '2012-05-27 10:41:57.805354' WHERE "companies"."id" = 2
   (10.1ms)  commit transaction
=> true
irb(main):006:0> c.destroy
   (0.2ms)  begin transaction
   (0.3ms)  SELECT MAX("audits"."version") AS max_id FROM "audits" WHERE "audits"."auditable_id" = 2 AND "audits"."auditable_type" = 'Company'
  SQL (1.2ms)  INSERT INTO "audits" ("action", "associated_id", "associated_type", "auditable_id", "auditable_type", "audited_changes", "comment", "created_at", "remote_address", "user_id", "user_type", "username", "version") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["action", "destroy"], ["associated_id", nil], ["associated_type", nil], ["auditable_id", 2], ["auditable_type", "Company"], ["audited_changes", "---\nname: bbb\n"], ["comment", nil], ["created_at", Sun, 27 May 2012 10:42:17 UTC +00:00], ["remote_address", nil], ["user_id", nil], ["user_type", nil], ["username", nil], ["version", 3]]
  SQL (0.3ms)  DELETE FROM "companies" WHERE "companies"."id" = ?  [["id", 2]]
   (3.7ms)  commit transaction
=> #<Company id: 2, name: "bbb", created_at: "2012-05-27 10:18:10", updated_at: "2012-05-27 10:41:57">
irb(main):007:0> 

ちょっとした注意点として1点。削除の時にdeleteを呼んだときは、auditsテーブルに対するinsertは行われません。

irb(main):007:0> c = Company.create(:name => "test1")
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "companies" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 27 May 2012 10:45:49 UTC +00:00], ["name", "test1"], ["updated_at", Sun, 27 May 2012 10:45:49 UTC +00:00]]
   (0.2ms)  SELECT MAX("audits"."version") AS max_id FROM "audits" WHERE "audits"."auditable_id" = 3 AND "audits"."auditable_type" = 'Company'
  SQL (0.4ms)  INSERT INTO "audits" ("action", "associated_id", "associated_type", "auditable_id", "auditable_type", "audited_changes", "comment", "created_at", "remote_address", "user_id", "user_type", "username", "version") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["action", "create"], ["associated_id", nil], ["associated_type", nil], ["auditable_id", 3], ["auditable_type", "Company"], ["audited_changes", "---\nname: test1\n"], ["comment", nil], ["created_at", Sun, 27 May 2012 10:45:49 UTC +00:00], ["remote_address", nil], ["user_id", nil], ["user_type", nil], ["username", nil], ["version", 1]]
   (3.0ms)  commit transaction
=> #<Company id: 3, name: "test1", created_at: "2012-05-27 10:45:49", updated_at: "2012-05-27 10:45:49">
irb(main):008:0> c.delete
  SQL (3.6ms)  DELETE FROM "companies" WHERE "companies"."id" = 3
=> #<Company id: 3, name: "test1", created_at: "2012-05-27 10:45:49", updated_at: "2012-05-27 10:45:49">
irb(main):009:0> 

これは、ActiveRecordがdeleteのときにはcallbackを呼ばないからです。

詳細は「Ruby on Rails Guides: Active Record Validations and Callbacks」を読んでおくと良いかと思います。

長くなってきたので、今日はここまで。