Rails3で論理削除するプラグイン

Rails3のActiveRecord実装の勉強がてらにRails3専用のレコード論理削除プラグイン 'Millstone' を作成しました。

gem 'millstone'
class User < ActiveRecord
  millstone    # または acts_as_paranoid
end

User.with_deleted                            # 削除済みも含めて全部取得
User.only_deleted                            # 削除済みのみ取得
User.administrators.with_deleted     # NamedScopeからも利用できる
User.first.comments.with_deleted    # 関連からももちろん利用できる

User.first.destroy                             # 論理削除する
User.first.destroy!                            # 物理削除する

つくろうと思った経緯

色々と理由はあるけど、一番の理由は、rails3_acts_as_paranoidに僕が望んでいる以下の機能がなかったから。

  • Rails2世代にあった削除済みを含めた関連を取得するオプション
belongs_to :with_deleted => true
  • 削除済みも含めて関連のレコードを取得 ( rails3_acts_as_paranoid では関連でwith_deletedを使うとSQLがおかしなことになる。
User.first.associations.with_deleted

rails3_acts_as_paranoid は、Rails2世代のacts_as_paranoidのようにパワフルにはできてない感じだった。READMEにはシンプルにしたって書いてあったしね。

rails3_acts_as_paranoidをfork、拡張しようと頑張ってみたんだけど、「削除済みも含めて関連のレコードを取得」の実装が、default_scopeを生かしつつうまく実装できなかった。。
そこでRails3のActiveRecordの勉強がてらに自作してみることにした。

実装について

Rails3のActiveRecordを見てみると、データベースを操作するようなメソッドは ActiveRecord::Relation に委譲するように実装されていたので、ActiveRecord::Relation を拡張することで対応してみた。
嬉しいことにActiveRecord::Relation#extendingという指定されたモジュールをextendしてくれるメソッドが用意されていたので、論理削除モデルだけに拡張モジュールを組み込むようにすることができた。

拡張モジュールで対応したことは以下の通り。

  • 物理削除メソッドを用意した
  • 既存の削除メソッドを論理削除に書き換えた
  • 検索フラグを用意して、最終的にクエリを発行する際にフラグの状態をみて、検索条件に論理削除の条件を追加するように処理を書き換えた


実装はしてみたものの、拡張しすぎて3.0.0系でも3.0.6未満は動かない始末・・・
バランスって大切だなぁとあらためて思った。


ライブラリを作ることは多々あるけど、Gemとしての公開は初めてです。
よかったら触ってみてください。