acts_as_versionedとacts_as_paranoid

acts_as_versionedはモデルの変更履歴をバージョンテーブルに取っておいてくれて、acts_as_paranoidはモデルの削除を物理削除じゃなく論理削除にしてくれるので、一緒に使えば変更を完全に取っておけるんじゃね?と思ったんだけどどうもうまくいかない。
acts_as_paranoidを使うと、元のモデルは論理削除されるんだけど、バージョンテーブルの方が物理削除されてしまう。

てことで、解法1

class User < ActiveRecord::Base
  acts_as_paranoid
  acts_as_versioned :association_options => {:dependent => nil}
end

acts_as_versionedの宣言時に上記のようなオプションを渡してやれば、モデルが削除されてもバージョンテーブル側は残り続ける。
ただしこの場合、acts_as_versionedは論理削除を更新とは見なしてくれないので「削除された」という情報はバージョンテーブルには残らない。

てことで、解法2

class UserVersions < ActiveRecord::Base
  acts_as_paranoid :with => 'versions_deleted_at'
end

class User < ActiveRecord::Base
  acts_as_paranoid
  acts_as_versioned :association_options => {:class_name => 'UserVersions', :dependent => :destroy}
end

バージョンテーブルにもacts_as_paranoidを設定して論理削除にする。これで削除されたことを後で捕捉できる。
ただし罠がいくつかあるので注意。

  • 元のテーブルもacts_as_paranoidしているのでバージョンテーブルではdeleted_atというカラム名は使えない。上の例のように:withオプションで異なるカラム名を設定するか、acts_as_versionedのnon_versioned_columnsを設定する
# 別解
class User < ActiveRecord::Base
  acts_as_paranoid
  acts_as_versioned :association_options => {:class_name => 'UserVersions', :dependent => :destroy}
  self.non_versioned_columns << 'deleted_at'
end
  • acts_as_versionedはバージョンモデルとしてUser::Version(どこで宣言されてるんだろう?)を使うことを仮定しているので、:class_nameオプションでUserVersionsモデルを使用するように設定する
  • acts_as_versionedは元モデルが削除されたときにdelete_allを使用してバージョンモデルを削除するけどそれだと物理削除されてしまうので、:dependentオプションでdestroyを使うように設定する

ただし、このやり方でも論理削除は更新じゃないので、モデル削除時にバージョンエントリが追加されるわけではない。

てことで、解法3
...は思いつきませんでした。