論理削除による退会機能
はじめに
エンジニア転職を目指しRuby on Railsを中心に学習中の初学者です。
備忘録として、躓いたことやケアレスミスも含め投稿します!誤っている箇所などありましたらご指摘いただけると幸いです
対象読者
- Ruby on Rails初学者
記事概要
論理削除とは簡潔に伝えると表面上は削除され非表示になるがデータベース上では保管されたままになる方法です。
物理削除は完全にデータベースから消去されます。
今回の実装ですが結論から申し上げるとgemを使った方が早いです。
gemを利用した参考記事↓
私は論理削除のgemがあることを知らずにgemなしで実装しました。参考になれば幸いです。
記事の前提条件
- usersモデルが作成済みであること
- Soceryを利用したログイン機能を実装していること
- デザインはbootstarapと一部scssで調整。
1. 退会申請フォームの作成
まず、退会申請フォームを作成します。ユーザーがこのフォームにアクセスすると、退会確認ページが表示され、退会の最終確認を行います。
手順
-
ルーティングの設定
config/routes.rb
に退会申請用のルートを追加します。ruby コードをコピーする Rails.application.routes.draw do # 退会確認ページ get 'users/withdraw', to: 'users#withdraw' # 退会処理 delete 'users/withdraw', to: 'users#destroy' end
-
コントローラの作成
退会確認ページと退会処理を担当するアクションを作成します。
class UsersController < ApplicationController before_action :require_login, only: [:withdraw, :destroy] def withdraw # 退会確認ページを表示 end def destroy # ユーザーを論理削除 current_user.update_attribute(:deleted_at, Time.current) # Sorceryのセッションを終了し、ホームページにリダイレクト logout redirect_to root_path, notice: '退会が完了しました。' end end
【参考】私の実際の実装
# app/controllers/users_controller.rb
def destroy
if current_user
current_user.update(deleted_at: Time.current) # 退会処理
logout # ログアウト処理
redirect_to root_path, notice: '退会処理が完了しました。'
else
redirect_to root_path, alert: '退会処理に失敗しました。'
end
end
notice:
とalert:
に関しては下記を参照
【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説 - Qiita
deleted_at
というカラムを追加し、その時刻を設定することで、データベースには残しつつ、通常のユーザーとしては扱わないようにできます。
rails g migration AddDeletedAtIndexToUsers
class AddDeletedAtIndexToUsers < ActiveRecord::Migration[7.1]
def change
add_index :users, :deleted_at
end
end
- ビューの作成
退会確認ページのビューを作成します。
app/views/users/withdraw.html.erb
erb
<div class="withdraw-container">
<div class="withdraw-wrapper">
<h1>退会確認</h1>
<p>本当に退会しますか? この操作は元に戻せません。</p>
<div class="withdraw-botton-list d-flex justify-content-center gap-3">
<%= button_to '退会する', users_withdraw_path, method: :delete, data: { turbo_confirm: '本当に退会しますか?' }, class: "btn btn-primary" %>
<%= button_to 'キャンセル', root_path, class: "btn btn-danger" %>
</div>
</div>
</div>
退会ボタンをクリックしたときにアラートを表示して、誤操作を防ぐために、data: { confirm: '本当に退会しますか?' }
を使用します。このオプションはJavaScriptによる確認ダイアログを表示します。
- 退会処理の保護
論理削除の場合、アプリケーション全体で、deleted_at
が設定されているユーザーを対象外とするために、モデルにスコープを設定することが一般的です。
app/models/user.rb
class User < ApplicationRecord
scope :active, -> { where(deleted_at: nil) }
# デフォルトスコープとして使用する場合
default_scope { active }
end
これで、User.active
またはデフォルトスコープを使用することで、論理削除されたユーザーを自動的に除外できます。
補足
default_scope
は、Rails のモデルにおいて、クエリが実行される際にデフォルトで適用される条件を設定するためのメソッドです。これを使うと、全てのクエリに対して特定の条件を自動的に追加できます。
default_scope
の使い方
以下のように default_scope
を定義します。
ruby
コードをコピーする
class User < ApplicationRecord
# 'deleted_at' が nil のレコードのみを取得する
default_scope { where(deleted_at: nil) }
end
この設定の効果
上記の設定を行うと、User
モデルを使ってデータを取得する際に、自動的に deleted_at
が nil
のレコード(論理削除されていないユーザー)だけが対象になります。
例えば、User.all
や User.find(1)
などの通常のクエリを実行した場合、deleted_at
が nil
であるユーザーだけが結果に含まれます。
ruby
コードをコピーする
User.all
# SELECT "users".* FROM "users" WHERE "users"."deleted_at" IS NULL
default_scope
のメリット
- 一貫性: アプリケーション全体で論理削除されたデータを除外することが容易になります。
-
コードの簡略化: 毎回
where(deleted_at: nil)
のような条件を書かなくても良くなります。
default_scope
の注意点
-
無効化が難しい: 特定のクエリで
default_scope
を無視したい場合、unscoped
を使う必要がありますが、これが少し面倒です。ruby コードをコピーする User.unscoped.all # SELECT "users".* FROM "users"
-
意図しない動作: 一部の操作(特に削除や更新)で、
default_scope
によって意図しないデータが除外されてしまう可能性があります。特に複雑なクエリを作成するときに影響を与えることがあります。
デフォルトでスコープを設定したいが、特定の操作では簡単に無効化したい場合は、明示的にスコープを定義して、それを使うようにする方が管理が楽です。
ruby
コードをコピーする
class User < ApplicationRecord
scope :active, -> { where(deleted_at: nil) }
end
これにより、必要に応じてスコープを選択して使うことができ、柔軟性が高まります。
default_scope
は便利ですが、使いどころに注意が必要です。
2. 退会済みユーザーのログイン制限
User
モデルの修正
1. 退会済みのユーザーをログインできないようにするため、User
モデルにログイン制限のロジックを追加します。具体的には、deleted_at
カラムが設定されている場合はログインを拒否するようにします。
class User < ApplicationRecord
authenticates_with_sorcery!
# ログイン可能なユーザーかどうかを判定
def active_for_authentication?
# Sorceryの標準のメソッドが利用可能であることを確認する
if respond_to?(:super)
super && deleted_at.nil?
else
# Sorceryが期待する標準的なメソッドがない場合のカスタム実装
!deleted_at.present?
end
end
# ログイン実行時に表示するメッセージを設定
def inactive_message
if deleted_at.present?
:deleted_account
else
super
end
end
end
-
active_for_authentication?
メソッドは、ユーザーがログインできるかどうかを判定します。deleted_at
カラムがnil
でない場合はログインを拒否します。 -
inactive_message
メソッドは、ログインしようとしたときに表示されるメッセージを設定します。deleted_account
はカスタムメッセージのキーです。
config/locales
にエラーメッセージを追加
2. config/locales/ja.yml
ファイルに、deleted_account
メッセージの翻訳を追加します。
yaml
コードをコピーする
ja:
sorcery:
session:
deleted_account: "このアカウントは退会済みです。再登録が必要です。"
ApplicationController
の確認
ApplicationController
に current_user
メソッドが正しく実装されているか確認します。このメソッドは、ログインしているユーザーを返す役割を果たします。
ruby
コードをコピーする
class ApplicationController < ActionController::Base
private
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
データベース容量を減らす為には物理削除の方が良いかと思います。どちらを選ぶかはサービスの趣旨に合わせてご検討ください
Discussion