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

インストール編、適用編に続く関連編です。過去2回はそれぞれ以下を参照してください。

UserモデルとCompanyモデルには次のような関連を定義しているとします。まずはapp/models/user.rb。

class User < ActiveRecord::Base
   audited
   belongs_to :company
end

次に、app/models/company.rb。

class Company < ActiveRecord::Base
   has_many :users
end

このときにCompany経由でUserを作ることができるのがRailsのすごいところです。

irb(main):001:0> c = Company.create(:name => "hoge")
   (0.1ms)  begin transaction
  SQL (19.6ms)  INSERT INTO "companies" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 27 May 2012 11:28:08 UTC +00:00], ["name", "hoge"], ["updated_at", Sun, 27 May 2012 11:28:08 UTC +00:00]]
   (12.8ms)  commit transaction
=> #<Company id: 5, name: "hoge", created_at: "2012-05-27 11:28:08", updated_at: "2012-05-27 11:28:08">
irb(main):002:0> u = c.users.create(:name => "foo")
   (0.1ms)  begin transaction
  SQL (0.7ms)  INSERT INTO "users" ("company_id", "created_at", "name", "updated_at") VALUES (?, ?, ?, ?)  [["company_id", 5], ["created_at", Sun, 27 May 2012 11:28:24 UTC +00:00], ["name", "foo"], ["updated_at", Sun, 27 May 2012 11:28:24 UTC +00:00]]
   (0.2ms)  SELECT MAX("audits"."version") AS max_id FROM "audits" WHERE "audits"."auditable_id" = 2 AND "audits"."auditable_type" = 'User'
  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", "User"], ["audited_changes", "---\nname: foo\ncompany_id: 5\n"], ["comment", nil], ["created_at", Sun, 27 May 2012 11:28:24 UTC +00:00], ["remote_address", nil], ["user_id", nil], ["user_type", nil], ["username", nil], ["version", 1]]
   (4.0ms)  commit transaction
=> #<User id: 2, name: "foo", company_id: 5, created_at: "2012-05-27 11:28:24", updated_at: "2012-05-27 11:28:24">
irb(main):003:0> 

ここで、ちょっと注目して欲しいのは、auditsテーブルにassociated_idとかassociated_typeといった関連に関するような項目があるということ。ここに値を入れるには、モデルに対してメソッドを呼び出してあげます。

Userモデルには、auditedに対して :associated_with を追記します。

class User < ActiveRecord::Base
   audited :associated_with => :company
   belongs_to :company
end

Companyモデルに対しては、 has_associated_audits を呼び出してあげます。

class Company < ActiveRecord::Base
   has_many :users
   has_associated_audits
end

これで、先ほどと同じようにCompanyとUserを作成します。

irb(main):001:0> c = Company.create(:name => "hoge")
   (0.1ms)  begin transaction
  SQL (18.4ms)  INSERT INTO "companies" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 27 May 2012 11:36:47 UTC +00:00], ["name", "hoge"], ["updated_at", Sun, 27 May 2012 11:36:47 UTC +00:00]]
   (3.6ms)  commit transaction
=> #<Company id: 6, name: "hoge", created_at: "2012-05-27 11:36:47", updated_at: "2012-05-27 11:36:47">
irb(main):002:0> u = c.users.create(:name => "foo")
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "users" ("company_id", "created_at", "name", "updated_at") VALUES (?, ?, ?, ?)  [["company_id", 6], ["created_at", Sun, 27 May 2012 11:36:51 UTC +00:00], ["name", "foo"], ["updated_at", Sun, 27 May 2012 11:36:51 UTC +00:00]]
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 6 LIMIT 1
   (0.2ms)  SELECT MAX("audits"."version") AS max_id FROM "audits" WHERE "audits"."auditable_id" = 3 AND "audits"."auditable_type" = 'User'
  SQL (0.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", "create"], ["associated_id", 6], ["associated_type", "Company"], ["auditable_id", 3], ["auditable_type", "User"], ["audited_changes", "---\nname: foo\ncompany_id: 6\n"], ["comment", nil], ["created_at", Sun, 27 May 2012 11:36:52 UTC +00:00], ["remote_address", nil], ["user_id", nil], ["user_type", nil], ["username", nil], ["version", 1]]
   (3.4ms)  commit transaction
=> #<User id: 3, name: "foo", company_id: 6, created_at: "2012-05-27 11:36:51", updated_at: "2012-05-27 11:36:51">
irb(main):003:0> 

associated_idにはCompanyのidである6が、associated_typeにはCompanyが入っています。

これを使って、Userの監査証跡情報からCompanyを取得することができます。

irb(main):004:0> u.audits.first.associated
  Audited::Adapters::ActiveRecord::Audit Load (0.5ms)  SELECT "audits".* FROM "audits" WHERE "audits"."auditable_id" = 3 AND "audits"."auditable_type" = 'User' ORDER BY version LIMIT 1
  Company Load (0.2ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 6 LIMIT 1
=> #<Company id: 6, name: "hoge", created_at: "2012-05-27 11:36:47", updated_at: "2012-05-27 11:36:47">
irb(main):005:0> u.audits.first.associated
  Audited::Adapters::ActiveRecord::Audit Load (0.5ms)  SELECT "audits".* FROM "audits" WHERE "audits"."auditable_id" = 3 AND "audits"."auditable_type" = 'User' ORDER BY version LIMIT 1
  Company Load (0.2ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 6 LIMIT 1
=> #<Company id: 6, name: "hoge", created_at: "2012-05-27 11:36:47", updated_at: "2012-05-27 11:36:47">
irb(main):006:0> 

逆に、Companyの関連する監査証跡情報も取得することができます。

irb(main):006:0> c.associated_audits
  Audited::Adapters::ActiveRecord::Audit Load (0.4ms)  SELECT "audits".* FROM "audits" WHERE "audits"."associated_id" = 6 AND "audits"."associated_type" = 'Company' ORDER BY version
=> [#<Audited::Adapters::ActiveRecord::Audit id: 7, auditable_id: 3, auditable_type: "User", associated_id: 6, associated_type: "Company", user_id: nil, user_type: nil, username: nil, action: "create", audited_changes: {"name"=>"foo", "company_id"=>6}, version: 1, comment: nil, remote_address: nil, created_at: "2012-05-27 11:36:52">]
irb(main):007:0> 

ひと通り、これでauditedの紹介はオシマイです。お疲れさまでした。