railsで退会処理を実装する際に考えなきゃいけないことや実装方法について解説していきます。この記事ではdiscard gemを用いた論理削除を採用しています。
論理削除と物理削除
まず退会処理を実装する際にユーザーデータなどを論理削除にするのか物理削除にするのかが分岐点になります。
論理削除と物理削除は、データベース管理におけるデータ削除戦略の二つの主要なアプローチです。それぞれには利点と欠点があり、使用するシナリオに応じて選択する必要があります。
論理削除
論理削除は、データをデータベースから実際には削除せず、削除されたとマークする方法です。これは通常、レコードにis_deleted
、deleted_at
などのフラグまたはタイムスタンプを設定することによって行われます。論理削除されたデータは、アプリケーションのロジックによって非表示にされますが、物理的にはデータベース内に残ります。
メリット
- データ復旧が容易: 誤って削除されたデータを簡単に復元できます。
- データの完全性: データの歴史を保持できるため、監査トレールや履歴追跡が容易になります。
- 安全な削除: データを即座に削除するのではなく、削除を示すことで、データの安全な取り扱いが可能になります。
デメリット
- データベースのサイズ: 削除されたデータが物理的に残るため、データベースのサイズが肥大化する可能性があります。
- クエリの複雑化: 論理削除されたレコードを除外するために、クエリに常に追加の条件を加える必要があります。
物理削除
物理削除は、データをデータベースから完全に削除する方法です。削除されたデータは復旧できず、データベースの容量を解放します。
メリット
- データベースのパフォーマンス: 不要なデータを削除することで、データベースのパフォーマンスを維持または向上させることができます。
- シンプルなクエリ: 物理削除後、削除されたデータに対する追加のフィルタリングは必要ありません。
デメリット
- データ復旧が困難または不可能: 一度削除されたデータは復旧できないため、誤って重要なデータを削除するリスクがあります。
- データの完全性: データの歴史や監査トレールが失われる可能性があります。
- 削除の取り扱い: データの依存関係によっては、物理削除が複雑になることがあります(例えば、外部キー制約がある場合)。
discard gemで論理削除
paranoidかdiscardか
paranoia
gemとdiscard
gemは、Railsアプリケーションで論理削除(ソフトデリート)を実装するためのライブラリです。論理削除とは、データベースからレコードを物理的に削除するのではなく、削除されたとマークして実際には保持し続けることを指します。これにより、誤ってデータを削除した場合でも復旧が可能になり、また、過去のデータを参照する必要がある場合に役立ちます。
大きな違いとしては
- paranoidはdestroyメソッドなので物理削除とロンロ削除の見分けがつきにくい
- paranoidはデフォルトのスコープが変わるので勝手に選択肢から除外される。そのへんの挙動をちゃんとコントロールしたいならdiscard
paranoid
-
paranoia
gemは、ActiveRecordモデルに論理削除の機能を追加します。 - モデルに
deleted_at
カラムを追加することで、レコードが削除された時刻を記録します。 - 削除されたレコードは、デフォルトのスコープから自動的に除外されます。
paranoidメリット
- 簡単に論理削除を実装でき、削除されたレコードを簡単に隠すことができます。
-
restore
メソッドを使って、削除されたレコードを簡単に復元できます。
paranoidデメリット
- 同じdestroyメソッドなので論理削除と物理削除の見分けがつかない
discard
-
discard
gemもまた、ActiveRecordモデルに論理削除を追加しますが、こちらはdiscarded_at
カラムを使用します。 - 削除されたレコードはデフォルトのスコープから除外されず、取得する際には明示的に指定する必要があります
discardメリット
- デフォルトスコープを変更しないため、予期しない挙動のリスクが低減されます。
- シンプルで直感的なAPIを提供し、論理削除と復元のプロセスを簡単に制御できます。
discardデメリット
- 削除されたレコードを除外したい場合、クエリに常に明示的なスコープを追加する必要があります。
-
paranoia
に比べると、自動的に関連レコードを扱う機能が限定的です。
discardによる退会機能実装
gem導入
gem 'discard'
bundle install
gemを上記で導入
カラム追加
discarded_atカラムを追加
class AddDiscardedAtToPosts < ActiveRecord::Migration[5.2]
def change
add_column :posts, :discarded_at, :datetime
add_index :posts, :discarded_at
end
end
modelにinclude Discard::Modelを追記
class Post < ApplicationRecord
include Discard::Model
end
実際にdiscardする
@post.discard
# 削除
post.discard # => true
# 確認
post.discarded? # => true
# 強制削除。既に削除済の場合は、exceptionが発生する。
post.discard! # => true
post.discard! # Discard::RecordNotDiscarded: Failed to discard the record
# 削除したレコードを元に戻す
post.undiscard # => true
post.undiscard! # => Discard::RecordNotUndiscarded: Failed to undiscard the record
post.discarded_at # => nil
# 削除した日時を確認
post.discarded_at # => Mon, 21 Oct 2019 14:34:41 JST +09:00
# 削除されたレコード一覧
Post.discarded # => [#<Post:0x00007fc04dbe3010 ...]
# 削除されていないレコード一覧
Post.kept # => []
allでdiscard以外を取ってくるようにmodelにkept記述
class User < ApplicationRecord
include Discard::Model # 追記
default_scope -> { kept }
end
allでdiscard以外を引っ張るようにする。
discardされたユーザーがログインできないように
class User < Activerecord::Base
include Discard::Model
# 略
def active_for_authentication?
super && kept?
end
end
superは、現在のメソッドの親クラスの同名のメソッドを呼び出すために使用されます。つまり、active_for_authentication?メソッドの実装において、親クラスで定義された同名のメソッドの処理を実行されます。
kept?は、discardで提供されるメソッドの一つで、論理削除されていない(つまり、discarded?がtrueでない)オブジェクトであるかどうかを判定するために使用されます。
つまり、super && kept?は、親クラスで定義されたactive_for_authentication?メソッドの戻り値がtrueであり、かつ、現在のユーザーオブジェクトが論理削除されていない場合に、active_for_authentication?メソッドがtrueを返すようにするための条件式。
メールで退会通知
メールで無事に退会できた旨を伝えると丁寧。
関連するデータをどうするか
退会処理を実装する際に困るのがどこまでデータを削除するか。ユーザーデータはもちろんだけど付随する投稿データ、コメントなど他社に影響するデータをどこまでするのかは難しい。
また削除する範囲を広げるとそれに関連付けされたデータにも影響を及ぼす。
どういったサービスなのか、ユーザーにどういったコミュニケーションを取るかによるが下記でパターン分けすると良さそう。
- ユーザー情報
- ユーザーが投稿したデータ
- ユーザーが他ユーザーに対して行ったアクション
1は確実に消すとして2は消すことでそのデータにアクションした人のデータなども関係してくるので要注意。
3は他社にも影響を及ぼしてしまうのでできればユーザーを名無しなどにして辿れないようにすることで対処したい。(しかし、これはユーザーとのコミュニケーションによる
ログアウトさせるべきか?
退会したあとにログアウトをさせないと退会したのにログインが必要なコンテンツを見れる状態になってしまう。結構盲点。