acts_as_paranoidで論理削除しているモデルを物理削除に戻す

はじめに

イタンジ株式会社の磯谷です。不動産賃貸仲介業向けのSaaSであるノマドクラウドの開発をしています。 今回は、Railsアプリケーションでacts_as_paranoidによって論理削除しているモデルを物理削除に戻すといったことを行ったので、それについて書いていこうと思います。

前提について

対象のモデルは、gemのparanoiaが提供するacts_as_paranoidを利用して論理削除を行っていました。ただ、このモデルについて論理削除されたレコードを後から利用していないことと、持っている情報の内容として物理削除の方が好ましいと考えられることから、論理削除をやめて物理削除に戻したいという動機がありました。

そこで今回は、以下をゴールとしました。

  • 対象のモデルについて物理削除されるようにすること
  • 既に論理削除されているレコードについては全て物理削除すること
  • 論理削除のために利用していたdeleted_atカラムを削除すること

作業内容について

うまくいかない方法

まず最初に素直に頭に浮かんだのは以下のような方法ですが、この方法だと不具合を起こしてしまいます。

  1. 物理削除に戻すためにモデルからacts_as_paranoidを外す
  2. 論理削除済みのレコードについてDELETE文を実行する
  3. deleted_atカラム削除のマイグレーションをする

なぜなら、acts_as_paranoidを外してから論理削除済みのレコードについてDELETE文を実行し終わるまでの間で、論理削除済みレコードが利用されてしまうためです。

例えば以下のような場合、User#postsの返り値で論理削除済みレコードのオブジェクトが含まれてしまいます。

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
- acts_as_paranoid
-
  belongs_to :user
end
user.posts
# => [
#      #<Post id: 1, user_id: 1, title: "post_01", ..., deleted_at: Fri, 31 Mar 2023 12:34:56.000000000 JST +09:00>,
#      #<Post id: 2, user_id: 1, title: "post_02", ..., deleted_at: nil>,
#      ...
#    ]

やったこと

そこで以下の作業を行いました。

  1. 物理削除に戻すモデルについて、acts_as_paranoidの代わりにdefault_scope を設定する
  2. 論理削除済みのレコードについてDELETE文を実行する
  3. 1で設定したdefault_scopeを外す
  4. deleted_atカラム削除のマイグレーションをする

前述の方法と異なる点は、論理削除済みのレコードを物理削除するまでの間で以下のように一時的にdefault_scopeを設定している点です。

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
- acts_as_paranoid
+ default_scope { where(deleted_at: nil) }

  belongs_to :user
end

default_scopeを設定することで、既に論理削除済みのレコードが利用されてしまうことを防いでいます。

default_scopeの利用に関しては、Rails Best Practicesのこちらの記事等で、他のスコープを利用したい場合にunscopedを利用しなくてはいけなくなる点であったり、モデルの初期化時にもそのスコープが反映されてしまう点が懸念点として挙げられています。

ただ、今回のケースでは他のスコープを利用する場面がないこととモデルの初期化時にdeleted_at: nilが反映されて問題なかったこと、また一時的な利用であることから問題ないと判断して利用しました。

おわりに

以上、acts_as_paranoidで論理削除しているモデルを物理削除に戻す作業の一例をまとめてみました。 今回は論理削除済みレコードが作業途中の間で利用されてしまうことをdefault_scopeで防ぐ方法を取りました。 この記事をご覧の方の一助になれば幸いです。