「paranoia」は使用非推奨なので、論理削除に「discard」を使う(Rails)

f:id:ryoutaku_jo:20190702223724p:plain

【結論】

paranoiaは公式が非推奨のアナウンスをしている

・理由は、ActiveRecordの機能をオーバーライドしてしまう為

discardというgemであれば、ActiveRecordをオーバーライドせずに論理削除が実装可能

【目次】

【本題】

paranoia」は使用非推奨

先日から何度か記事に上げている論理削除について、今回もまとめます。

ryoutaku-jo.hatenablog.com

ryoutaku-jo.hatenablog.com

ryoutaku-jo.hatenablog.com

前回からparanoiaについて調査を進めていましたが、公式の READMEに、以下の記述がありました(なぜ見逃していた・・・)

Notice: paranoia has some surprising behaviour (like overriding ActiveRecord's delete and destroy) and is not recommended for new projects. See discard's README for more details. Paranoia will continue to accept bug fixes and support new versions of Rails but isn't accepting new features.

要約するとparanoiaは、ActiveRecorddeletedestroyをオーバーライドするなど、予期しない動作をするので、新規プロジェクトでの使用は推奨しない」という内容です。

色々懸念点はあるものの、ローカル環境で動かす限りは正常に論理削除が動作していたので、このまま使おうとも思いましたが、公式が使用を推奨しないのであれば、継続して使い続けるには難があるので、今回paranoiaは使用しない事にしました。

とはいえ論理削除の動作を一から作り上げるのは非常に手間なので、gemで対応したいところ・・・

論理削除を実装するgem

論理削除の機能を提供するgemはparanoia以外にも複数存在します。

www.ruby-toolbox.com

ダウンロード実績の多いgemから探すとpaper_trailというものがありましたが、こちらは削除データを別テーブルに保持する仕様の様で、関連テーブルのデータの保持に難がありました。

次にacts_as_paranoidというgemを見つけましたが、こちらもActiveRecordをオーバーライドするという点ではparanoiaと一緒でした。

そこでparanoiaと比較するとスターやダウンロード実績は少ないですが、paranoiaのREADMEで紹介されていたdiscardというgemを利用してみる事にしました。

github.com

discardについて

discardは、paranoiaと同様に論理削除を実装する為のgemです。

paranoiaと異なり、ActiveRecordをオーバーライドすることはなく、レコードの論理削除/復元/参照のためのメソッドとスコープを利用する事ができます。

導入方法

まず、gemを導入します。Gemfileに以下を記述して、bundle installします。

gem 'discard'

カラムの追加とマイグレーション

次にparanoiaを適用させるテーブルにdiscarded_atカラムを追加します。

rails generate migration add_discarded_at_to_posts discarded_at:datetime:index
class AddDiscardToPosts < ActiveRecord::Migration[5.0]
  def change
    add_column :posts, :discarded_at, :datetime
    add_index :posts, :discarded_at
  end
end

こちらの内容でマイグレーションを行います。

これで最低限の機能実装は完了です。

使用方法(削除と復元)

論理削除を行う場合はdiscardメソッドを利用します。

これでdiscarded_atカラムに日時が記録され、削除フラグとなります。

復元する場合はundiscardメソッドを利用します。

post. discard

post.undiscard

使用方法(検索)

論理削除したデータ以外の全件検索する場合はkeptメソッドを利用します。

論理削除したデータのみ検索する場合はdiscardedメソッドを利用します。

Post.kept 

Post.discarded

使用方法(スコープの設定)

なおdiscardと違い、デフォルトのスコープにdiscarded_atカラムの有無を条件に含めていない為、allメソッドなどで検索すると、論理削除したデータも抽出されてしまいます。

もしデフォルトのスコープに論理削除したデータを含めない様にする場合は、モデルに以下の記述を行う必要があります。

class Post < ActiveRecord::Base
  include Discard::Model
  default_scope -> { kept }
end

論理削除したデータを検索する場合はwith_discardedメソッドを利用します。

また論理削除したデータだけを抽出する場合はwith_discarded.discardedとメソッドチェーンさせて抽出します。

Post.all                       # Only kept posts
Post.with_discarded            # All Posts
Post.with_discarded.discarded  # Only discarded posts

使用方法(関連モデルの設定)

なおActiveRecordのアソシエーションのオプションでdependent: :destroyを設定していても、discardメソッドでは、アソシエーション先の子モデルが削除される事はありません。

親モデルが論理削除されている時に、子モデルを非表示にしたい場合は、子モデルにスコープを設定する必要があります。

デフォルトで設定するのであれば、以下の様に記述します。

class Comment < ApplicationRecord
  belongs_to :post

  include Discard::Model
  default_scope -> { joins(:post).merge(Post.kept) }
end

参考情報

GitHub - jhawthorn/discard: 🃏🗑 Soft deletes for ActiveRecord done right

《今日の学習進捗(3年以内に10000時間に向けて)》

会社の定例で現在携わっているプロジェクトの今後の構想を聞かせて頂いたが、世界規模でシェア獲得を目指すなど想像以上に壮大で、非常にやりがいを感じた。

突貫で組み込んだことで拡張性が犠牲になった箇所や、パフォーマンスに難がある箇所など、今後の障害となり得る課題が山積しており、一朝一夕にはいかないだろうが、ぜひ実現に向けて開発に努めていきたい。

なお運用と並行して開発を進める中で、データの整合性を保つ難しさを改めて感じている。

今後も機能拡張は進めていく事になるが、データの整合性が破綻すれば致命的なバグが発生し兼ねない為、機能実装はこれまで以上に慎重に進める必要があると考えている。

直近では、現在着手しているユーザー削除機能(論理削除)は、実装方法を誤ればデータの整合性が取れなくなり、今後の負債となり兼ねないので、慎重に実装を進めていきたい。

論理削除については、実装方法は複数存在するのと、人によってはそもそも推奨していなかったりするので、何がベストプラクティスなのか、なかなか判断難しい。

一先ずは致命的なバグを回避できることに重点を置いて、開発を進めていきたい。

学習開始からの期間 :207日
今日までの合計時間:2007h
一日あたりの平均学習時間:9.7h
今日までに到達すべき目標時間:1890h
目標との解離:117h
「10,000時間」まで、

残り・・・「7993時間!」