論理削除を実現してくれるgem、acts_as_paranoidを試してみました。
環境
Ruby2.1.5
Rails4.2.0
インストール
まずはGemfileに以下のように追加してbundle install。
gem 'acts_as_paranoid'
しかしWEBrickを起動しようとすると下記エラー。
どうやらrails4系に対応してない模様。
undefined method `validate_find_options' for class `Class' (NameError)
こちらのリポジトリでメンテナンスされているようなのでGemfileを下のように書き換えて再度bundle install。
これで無事にWEBrickが起動してくれるようになりました。
gem 'acts_as_paranoid', github: 'ActsAsParanoid/acts_as_paranoid'
設定
acts_as_paranoidを使う設定は下の2ステップになります。
1. 論理削除をしたいテーブルにdeleted_atカラムを追加する。
2. 以下のように論理削除したいテーブルでacts_as_paranoidを呼び出す。
class Task < ActiveRecord::Base acts_as_paranoid end
acts_as_paranoidの挙動
具体的なacts_as_paranoidの挙動は下記の通りです。
destroyメソッドで物理削除されなくなる
delete, destroy, delete_all, destroy_allを試してみましたが、全て物理削除からdeleted_atに現在時刻を入れる挙動に変わっていました。
2.1.5 :047 > Task.delete_all SQL (1.3ms) UPDATE `tasks` SET deleted_at = '2015-01-04 13:51:26.703429' WHERE (`tasks`.`deleted_at` IS NULL)
find系で削除したものを取得しないようにする
find, all, first, last等、検索をかける際は全てdeleted_atがnullのものを取得する挙動に変わっていました。
2.1.5 :046 > Task.all Task Load (0.3ms) SELECT `tasks`.* FROM `tasks` WHERE (`tasks`.`deleted_at` IS NULL) => #<ActiveRecord::Relation [#<Task id: 2, name: "aaaaaa", deleted_at: nil, created_at: "2015-01-04 13:20:49", updated_at: "2015-01-04 13:48:36">]>
コードを見たところdefault_scopeを使っているようです。
# Magic!
default_scope { where(paranoid_default_scope_sql) }
def paranoid_default_scope_sql if string_type_with_deleted_value? self.all.table[paranoid_column].eq(nil). or(self.all.table[paranoid_column].not_eq(paranoid_configuration[:deleted_value])). to_sql else self.all.table[paranoid_column].eq(nil).to_sql end end
削除されたものを取得できるメソッドが追加
削除されたレコードを取得するonly_deletedや削除済も未削除も全て取るwith_deletedが追加されています。
あとは削除時間を元に取得するdeleted_after_timeやdeleted_before_timeもありました。
しかしデフォルトスコープが効いているため、with_deletedと併用しないと取ってこれないようです。
Task.deleted_before_time(Time.now) # これだと取ってこれない Task.with_deleted.deleted_before_time(Time.now) # これなら取ってこれる
削除されたものを復活させるメソッドが追加
削除されたものを復活する為のrecoverメソッドが追加されています。
あとrecover時に呼ばれるコールバックのbefore_recoverとafter_recoverも追加されています。
その他
ドキュメントを読んだ所、削除情報はDatetime型でなくString型やBoolean型で持つ事もできるようです。
まとめ
取得や削除を置き換えてくれるので使う側で意識する点が少ない所が良さそうです。
関連テーブルの挙動とかも機会あれば追いかけてみようと思います。