こんにちは、hachi8833です。
データベースで何かと問題になる論理削除ですが、他所から引き取ったRailsプロジェクトでparanoia gemが使われている場合に止むを得ず物理削除を行うための方法をメモします。記事末尾のコメントもご覧ください。
paranoiaでの物理削除
ActiveRecordオブジェクトで#really_destroy!
を呼べば物理削除になります。
>> client.really_destroy! # => client
それ以外のdestroy
系操作は期待どおり論理削除(実際には削除せず、隠すだけ)になります。
注意: 以下にあるように、関連するすべてのdependent: :destroy
レコードも物理削除されます。
WARNING: This will also really destroy all dependent: :destroy records, so please aim this method away from face when using.
rubysherpas/paranoia
確認したところ、paranoiaの#deleteで以下のメソッドがオーバーライドしているので、#deleteでは物理削除されなくなります。
def delete
raise ActiveRecord::ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
if persisted?
# if a transaction exists, add the record so that after_commit
# callbacks can be run
add_to_transaction
update_columns(paranoia_destroy_attributes)
elsif !frozen?
assign_attributes(paranoia_destroy_attributes)
end
self
end
以上です。
paranoia gemの利用法
基本的な利用法もメモします。paranoiaのリポジトリそのまんまですが。
モデル(ここではClient)でacts_as_paranoid
を追加することで論理削除が有効になります。
class Client < ActiveRecord::Base
acts_as_paranoid
# ...
end
以後、このモデルのインスタンスでdeleted_at
とdestroy
を呼んでもdeleted_at
タイムスタンプが追加されるだけで実際には削除されなくなります。
>> client.deleted_at # => nil
>> client.destroy # => client
>> client.deleted_at # => [タイムスタンプ]
よく使いそうなメソッドとスコープ
Client.all
に論理削除済みのものも含める場合:
Client.with_deleted
Client.without_deleted #=> 'without_default_scope'を指定しているなどの理由でdefault_scopeが使えない場合向け
論理削除済みのものだけ取り出す:
Client.only_deleted
論理削除済みかどうかのチェック方法(2種類):
client.paranoia_destroyed?
client.deleted?
論理削除を取り消す方法(2種類)
Client.restore(id)
client.restore
コメント
morimorihogeです。最近はコメントの人化してますね(汗
論理削除というとはてブ界隈で定期的にマサカリが飛び交う界隈ですが、大方の議論はDELETE_FLAG を付ける前に確認したいことで網羅されているので、投げる前にまずはこちらを参照しましょう。その上で議論する方がお互い有意義です。
#社内Slackでも定期的に話題になります
僕自身はRailsにおける論理削除はunique indexがどこかのカラムに付いてるとActiveRecordのuniqueness validationをくぐり抜けてSQLがエラーになったり、後から論理削除対応しようとするとdefault_scope周りで問題になったりで困ることが多いので、基本使わない派です。ブツリサクジョコワクナイヨ
ただ、どこかの誰かが作ったコードが保守・引き継ぎで回ってくることまでは避けられないので辛みですね。db/schema.rbをとりあえず見たときにステータスっぽいフラグが大量にあるテーブルを見ると心が荒みます(フラグなのにbool以外な値が入ってる奴とかは特に)。
ところでRailsではあまりRDBMSのVIEWを使わない文化な感じですが、複雑なscopeでフィルタリングされた条件を参照するときなんかはscopeよりもVIEWの方がシンプルじゃないかなあと思うことはあります。
たとえば、商品テーブル(products)があったときに「今販売可能な商品」という条件が「公開フラグがON」かつ「現在時刻が販売期間内含まれている」かつ「在庫がある」・・・(以下略)みたいなことは良くあると思うのですが、scopeで条件を作ってしまうとRailsコードからアクセスするのは便利なのですが、同等の条件を取り出すSQLが欲しい、となったときに色々不便です。
こんな時「v_current_available_products」の様なVIEWを定義しておけば、何も考えずにVIEWから取り出すことができるんですよね。VIEWはSELECTする限りにおいては通常のテーブルと同じように使えますし。
#scopeだと引数付きscopeなんかをメソッドチェーンで使えるメリットはあるっちゃありますが、同等のVIEWは作れるはず
と思ってここまで書いていたらVIEWを使われている事例がありました (DBのViewを作ったらRailsプログラムが綺麗になった話)。まあ、RailsでVIEWを扱う時はmigrationの絡みも出てきてまた違う辛みも出てくるので万人にオススメできるかというと辛いところもあるのですが、選択肢としてはアリだと思います。
ただ、MySQLにはマテリアライズドビューがないので、VIEWを多用するようなプロジェクトではPostgreSQLを使うか、更新頻度がそう頻繁でなくて良ければ擬似的にマテリアライズドビュー相当のテーブルを作るということになります。(参考:MYSQLで疑似的なマテリアライズド・ビュー作成)
いつの間にか話題がそれましたが、論理削除に限らずフラグやステータスについてはよくよく考えて行きたいものですね。「XXさんに聞かないとこのフラグの意味は分からない」とかにはなりたくないモノです(しみじみ)。
関連記事
- 論理削除(gem paranoia) Rails 4系
- ActiveRecordのRangeHandlerクラスとRubyの範囲メソッドRange#exclude_end?
- [Rails 4.0] ActiveRecordのコールバックが呼ばれる順番まとめ