Rails で論理削除を簡単に実装できる paranoia の使用方法です。論理削除自体の是非には触れません。
paranoiaのインストール
Gemfile
に以下を追加してbundle install
を実行してください。
gem 'paranoia'
paranoiaの使用方法
論理削除を実装したいモデルにdeleted_at
カラムを追加します。
$ ./bin/rails g model user name age:integer deleted_at:datetime $ ./bin/rails db:migrate
モデルファイルにacts_as_paranoid
の一文を記述してください。
class User < ApplicationRecord acts_as_paranoid end
以上で準備は完了です。実際に試してみます。
# 通常の where によるクエリですが条件に deleted_at が追加されています。 > User.where('age > ?', 20) User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND (age > 20) #=> #<ActiveRecord::Relation [#<User id: 1, name: "Jonh", age: 30, deleted_at: nil, created_at: "2017-08-13 08:35:19", updated_at: "2017-08-13 08:37:33">]> # destroy を実行すると物理削除ではなく論理削除になります。 > user = User.find(1) > user.destroy SQL (1.3ms) UPDATE "users" SET "deleted_at" = '2017-08-13 08:36:04.864701', "updated_at" = '2017-08-13 08:36:04.864875' WHERE "users"."id" = ? [["id", 1]] > user.deleted_at => Sun, 13 Aug 2017 08:36:04 UTC +00:00 # 論理削除後に検索しても取得されません。 > User.where('age > ?', 20) User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND (age > 20) => #<ActiveRecord::Relation []> # really_destroy! を実行すると従来の物理削除クエリが発行されます。 > user.really_destroy! SQL (1.5ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 1]]
以下は状況に応じた使用方法です。
論理削除カラム名の変更
paranoia のデフォルトではdeleted_at
が論理削除のカラム名として使われます。違うカラム名を使用したい場合はcolumn
オプションで指定して下さい。
class User < ApplicationRecord acts_as_paranoid column: :destroyed_at end
デフォルトスコープの追加をオフ
論理削除は使いたいがクエリのデフォルトスコープではオフにしておきたい場合もあります。without_default_scope
をtrue
にしましょう。
class User < ApplicationRecord acts_as_paranoid without_default_scope: true end
クエリの検索を実行してもdeleted_at
が条件に含まれません。
> User.where('age > ?', 20) User Load (0.3ms) SELECT "users".* FROM "users" WHERE (age > 20)
オフになるのは検索時なのでdestroy
を実行した場合は論理削除になります。
> user.destroy (0.1ms) begin transaction SQL (0.4ms) UPDATE "users" SET "deleted_at" = '2017-08-13 09:18:06.256660', "updated_at" = '2017-08-13 09:18:06.256829' WHERE "users"."id" = ? [["id", 2]]
削除済みのレコードも検索
User.all
を実行するとdeleted_at IS NULL
の条件が含まれますが、論理削除済みのレコードを取得したい場合はwith_deleted
で検索します。without_deleted
はwithout_default_scope: true
を指定している場合にUser.all
がdeleted_at
を参照しなくなるので逆に論理削除を除きたいときに使用します。
> User.all User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL > User.with_deleted User Load (0.1ms) SELECT "users".* FROM "users" > User.without_deleted User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL
all
だけでなく、where
などのクエリに付けることも可能です。
> User.where('age > ?', 20).with_deleted User Load (0.2ms) SELECT "users".* FROM "users" WHERE (age > 20)
削除済みのレコードのみを検索したければonly_deleted
を使用します。
> User.only_deleted User Load (0.3ms) SELECT "users".* FROM "users" WHERE ("users"."deleted_at" IS NOT NULL)
論理削除済みかどうかの判定
deleted?
またはparanoia_destroyed?
で削除済みレコードか否かを判定できます。
> user = User.only_deleted.first => #<User id: 2, name: "Jonh", age: 30, deleted_at: "2017-08-13 09:18:06", created_at: "2017-08-13 09:04:15", updated_at: "2017-08-13 09:18:06"> > user.deleted? => true > user.paranoia_destroyed? => true
論理削除から復旧させる
論理削除されたレコードを元に戻すにはrestore
を使用します。復数のIDを配列で指定する場合は、存在しなければActiveRecord::RecordNotFound
が発生するので気を付けましょう。
> user.restore SQL (0.4ms) UPDATE "users" SET "deleted_at" = NULL, "updated_at" = '2017-08-13 09:44:52.206675' WHERE "users"."id" = ? [["id", 2]] > User.restore(2) # 配列で復数のID指定も可 SQL (0.3ms) UPDATE "users" SET "deleted_at" = NULL, "updated_at" = '2017-08-13 09:45:22.548918' WHERE "users"."id" = ? [["id", 2]]