Tech Racho エンジニアの「?」を「!」に。
  • 開発

論理削除用paranoia gemがあるRailsプロジェクトで物理削除する方法

こんにちは、hachi8833です。

データベースで何かと問題になる論理削除ですが、他所から引き取ったRailsプロジェクトでparanoia gemが使われている場合に止むを得ず物理削除を行うための方法をメモします。記事末尾のコメントもご覧ください。

160915_1557_nhXqGK

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

160915_1638_Ae7tXA

確認したところ、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_atdestroyを呼んでも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さんに聞かないとこのフラグの意味は分からない」とかにはなりたくないモノです(しみじみ)。

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。