TojiTech No Programming, No Life.

RailsのDeviseでユーザーの論理削除を実装

背景

  • 物理削除だとユーザーの動向が追えず心配
  • ユーザー退会後の同じメールアドレスで登録できるようにしたい

サンプルリポジトリ

こちらから、実装済みソースコードが見られます。

注意

サンプルのリポジトリは、Dockerで起動することを前提に作成されています。
Dockerを使用しない場合は、appフォルダ以下で通常のrailsの起動処理を行ってください。

なお、記事ではDockerを使用せず導入する手順で記載していますのでご注意ください。

Rails6で記述していますが、Rails5でも同様に動作すると思います。

ソースコードのコメントについて

基本的にコメントを多めに記載しています。こちらを参考に実装して、ロジックの理解を深めてください。

Seedファイルにユーザーの初期情報が入っています。ユーザーの作成が面倒な場合にご利用ください。

作業

# Deviseのインストール
rails g devise:install

# DeviseのUserモデルを作成
rails g devise User

# コントローラーをオーバーライドするため必要
bundle exec rails g devise:controllers users

# ビューのカスタマイズ用のため(今回は未使用)
bundle exec rails g devise:views users

# この後使うカラムをusersテーブルに追加
rails g migration AddDeletedAtColumnToUsers deleted_at:datetime --table users

# マイグレーションの実行
rails db:migrate

# とりあえずhomesで色々とするのでコントローラー作成
rails g controller homes index

ルーティングをオーバーライドするコントローラーに向ける

Rails.application.routes.draw do
  root to: 'homes#index'
  devise_for :users, controllers: {
    registrations: 'users/registrations',
    sessions: 'users/sessions'
  }
end

とりあえず、の暫定トップページを作成

<h1>Homes#index</h1>
<p>Find me in app/views/homes/index.html.erb</p>

<ul>
  <li>
    <%= link_to '新規登録', new_user_registration_path %>
  </li>
  <li>
    <%= link_to 'ログイン', new_user_session_path %>
  </li>
  <%# ログイン済みならログアウト・退会リンクを表示 %>
  <% if user_signed_in? %>
    <li>
      <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
    </li>
    <li>
      <%= link_to '退会', user_registration_path, method: :delete, data: { confirm: "アカウントを削除してもよろしいですか?" } %>
    </li>
  <% end %>
</ul>

退会処理をオーバーライド

退会後も同じメールアドレスで登録できるようにsoft_deleteで変更しているところがポイント

# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  # def create
  #   super
  # end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  def destroy
    # 論理削除処理
    soft_delete(current_user)
    # Deviceの論理削除後の後処理
    respond_with_navigational do
      # 強制ログアウト
      sign_out current_user
      # ログアウト後のページ遷移
      redirect_to root_path
    end
  end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end

  private

  def soft_delete(user)
    # 同じメールアドレスでも登録できるように、
    # メールアドレスを“hoge@example.com_deleted_**********”に変更する
    deleted_email = user.email + '_deleted_' + Time.current.to_i.to_s
    user.assign_attributes(email: deleted_email, deleted_at: Time.current)

    # 通常メールアドレスが変更されると通知メールが飛ぶので、
    # その通知メールをキャンセルする
    user.skip_email_changed_notification!

    # 保存処理
    user.save
  end
end

ログインをオーバーライド

メールアドレスを論理削除で変更しているが、万が一書き換えたメールアドレスが判明された場合、ログインできてしまうことを考慮して認証をしっかりとしています。

# frozen_string_literal: true

class Users::SessionsController < Devise::SessionsController
  # before_action :configure_sign_in_params, only: [:create]

  # GET /resource/sign_in
  # def new
  #   super
  # end

  # POST /resource/sign_in
  def create
    user = User.find_by(email: params[:user][:email])
    if user.nil?
      # メールアドレスに該当がない場合は、新規登録画面に遷移
      #   ユーザー登録されていない旨のフラッシュメッセージを仕込むならこのセクション
      redirect_to new_user_registration_path
    else
      # パスワードの確認と、deleted_atにデータたが入っていないか
      #   deleted_atに日付が入っていれば再度ログイン画面へ遷移させる。
      if user.valid_password?(params[:user][:password]) && user.deleted_at.nil?
        # ログイン成功
        #   ログイン成功のフラッシュメッセージを仕込むならこのセクション
        # ---
        # ログイン情報をユーザー側に記録
        sign_in user
        # ログイン成功時の遷移
        redirect_to root_path
      else
        # ログイン失敗時の遷移
        # ---
        #   ログイン失敗のフラッシュメッセージを仕込むならこのセクション
        redirect_to new_user_session_path
      end
    end
  end

  # DELETE /resource/sign_out
  # def destroy
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_in_params
  #   devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
  # end
end

最後に

以上で、実装は完了のはずです。うまく動かない場合は、サンプルリポジトリを確認してください。

Profile

Yuki Tojima

RubyやPhp、JavaScriptまわりのことを徒然と記録に残す技術ブログです。

至らぬところもあると思いますが、見守っていただけると幸いです。

記事のリクエストや、間違いなどありましたら X (旧 Twitter) のDMなどでお気軽にご連絡ください。

ytojima @TojiTech
プロフィール画像