RailsのActiveRecordの削除(delete)は、通常、物理削除になっており、実際にデータがデータベースから削除されます。それに対して、論理削除は実際にはデータを削除せずに、削除されたと見なすフラッグと呼ばれるカラムを設定して、ユーザーには削除しているかのように振る舞い、必要時には元の状態に戻せるものです。「ユーザーが退会した後も、システム上一定期間はユーザーのデータを保持しなければならない。」や「違反等があり、一時的にアカウントを凍結したい」などの場合に、論理削除が使えると思います。
Railsではparanoiaというgemライブラリを利用することで簡単に実装することができます。このgemはacts_as_paranoidというgemを再実装したものです。
GitHub
https://github.com/rubysherpas/paranoia
実行環境はこちらです
1 2 3 |
ruby 2.5.1 rails 5.1.6 paranoia 2.4.1 |
Gemのインストール
Gemfile
1 |
gem 'paranoia' |
$ bundle install します。
カラムの追加
論理削除したいモデルに deleted_atとindexカラムを追加します。
1 |
$ rails g migration AddDeletedAtToUsers deleted_at:datetime:index |
マイグレーションファイルは以下の通りです。
1 2 3 4 5 6 |
class AddDeletedAtToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :deleted_at, :datetime add_index :users, :deleted_at end end |
$ rails db:migrate を実行します。
モデル
対象モデルに acts_as_paranoid を追記します。
1 2 3 |
class User < ApplicationRecord acts_as_paranoid end |
これでparanoiaの導入は完了です。
コンソールで試してみる
通常通りに destroy を実行すると、deleted_atにタイムスタンプが入り、物理削除ではなく論理削除になります。
1 2 3 |
> user = User.find(1) > user.destroy SQL (0.9ms) UPDATE "users" SET "deleted_at" = '2018-10-02 02:28:19.876082', "updated_at" = '2018-10-02 02:28:19.876202' WHERE "users"."id" = ? [["id", 1]] |
再度、findしても論理削除されたデータは取得できません。
1 2 3 4 |
> user = User.find(1) User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] ActiveRecord::RecordNotFound: Couldn't find User with 'id'=1 [WHERE "users"."deleted_at" IS NULL] from (irb):9 |
物理削除をしたい場合は really_destroy! を実行します。
1 2 |
> user.really_destroy! SQL (0.2ms) DELETE FROM "users" WHERE "users"."id" = ? [["id", 1]] |
削除したデータの確認
論理削除したデータのみ取得する場合です。
1 2 3 4 5 6 7 8 9 10 11 12 |
> User.only_deleted User Load (0.4ms) SELECT "users".* FROM "users" WHERE ("users"."deleted_at" IS NOT NULL) # 又は > User.only_deleted.first User Load (0.6ms) SELECT "users".* FROM "users" WHERE ("users"."deleted_at" IS NOT NULL) ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] # ID指定の場合 > User.only_deleted.find(4) User Load (0.3ms) SELECT "users".* FROM "users" WHERE ("users"."deleted_at" IS NOT NULL) AND "users"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]] |
論理削除したデータとしていないデータを合わせて取得する場合です。
1 2 |
> User.with_deleted User Load (0.5ms) SELECT "users".* FROM "users" |
論理削除したデータを除いたデータ場合です。
1 2 |
> User.without_deleted User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."deleted_at" IS NULL |
論理削除から元に戻す
restoreを実行します。
1 2 3 4 5 6 7 8 9 |
> user.restore (0.2ms) begin transaction SQL (0.5ms) UPDATE "users" SET "deleted_at" = NULL, "updated_at" = '2018-10-02 02:56:51.125278' WHERE "users"."id" = ? [["id", 3]] # ID指定の場合 > User.restore(2) User Load (0.8ms) SELECT "users".* FROM "users" WHERE ("users"."deleted_at" IS NOT NULL) AND "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]] (0.1ms) begin transaction SQL (0.5ms) UPDATE "users" SET "deleted_at" = NULL, "updated_at" = '2018-10-02 02:59:57.185702' WHERE "users"."id" = ? [["id", 2]] |
論理削除カラム名の変更
デフォルトではdeleted_atが論理削除のカラム名として使われますが、別名にしたい場合はcolumnオプションで指定します。
1 2 3 4 5 |
class Client < ActiveRecord::Base acts_as_paranoid column: :destroyed_at ... end |
デフォルトスコープをスキップしたい
論理削除は使いたいがデフォルトスコープはスキップしたいという場合です。
1 2 3 4 5 |
class Client < ActiveRecord::Base acts_as_paranoid without_default_scope: true ... end |
Callbacks
コールバックは以下のように、メソッドを呼ぶことができます。
1 2 3 4 5 6 7 |
class User < ActiveRecord::Base acts_as_paranoid after_destroy :update_document_in_search_engine after_restore :update_document_in_search_engine after_real_destroy :remove_document_from_search_engine end |
以上です!
そのほか、追いきれていないところもありますので、ドキュメントも参考にしていただけたらと思います!