[Rails] 退会済みユーザーをログインできないようにしてみた。[devise]

# はじめに
devise.gem を用いた標準的なユーザー認証は、先人の方々がQiita等に記載してくださってあるのですが、「退会したユーザーはログイン不可にする」といった、divise 機能の上書きが必要な内容に関しては見当たらなかったので、備忘録も兼ねて記していきたいと思います！

devise.gem の導入などの基本的な部分に関しては、先輩方の分かりやすい記事が沢山転がっているので、割愛させていただきます。m(_ _)m

# 実装したいこと
①enum を用いたユーザーステータスの実装
②退会済みユーザーはログイン不可にする。

# 設計について
上記内容を説明する上で、土台となる設計部分です。

### User テーブル定義
Userテーブルには、説明のために基本的な内容としています。
statusカラムのデータ型が、integer型となっているのは、この後説明するenum を用いてユーザーステータスを定義するためです。 

| カラム名 | 説明 | データ型 |
|:-:|:-:|:-:|
| ID  | ユーザーID  | integer  |
| name  | ユーザー名前  | string  |
| email  | メールアドレス  | string  |
| status  | ユーザーステータス  | integer  |
| created_at  | 作成日  | datetime  |
| updated_at  | 更新日  | datetime  |

### ユーザーステータスについて （status カラム）
ユーザーのステータスに関しては、以下の通りとなっています。

| status名 | 説明 |
|:-:|:-:|
| normal  | 通常  |
| withdrawn  | 退会済み  |

# 本記事の本題
前提事項の説明が少々長くなってしまいましたが、以下より本記事の本題となります〜！！

## ①enum を用いてユーザーステータスを実装する
下記のコードのように enum(列挙型) により、上記で述べたユーザーステータスをそれぞれ割り当てています。


```models/user.rb 
class User < ApplicationRecord

# devise
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

# validation (name, status を必須にしています。)
  with_options presence: true do
    validates :name
    validates :status
  end

# enum にてユーザーステータスを実装
  enum status: {
    normal: 0,    # ”通常”　ステータス
    withdrawn: 1, # "退会済み”　ステータス
  }
end
```
ちなみに、enum の詳しい説明に関しましては、下記の記事が大変わかりやすくご丁寧に説明してくださっています！
https://qiita.com/ozackiee/items/17b91e26fad58e147f2e

## ②退会済みユーザーはログイン不可にする
ユーザーがログインをする際に、退会済みのアカウントか否かを判定し、退会済みだった場合にはログインできないようにしていきます。

ログインをする際に、バリデーションを追加する場合は、devise で扱っているモデルの  
 `active_for_authentication?` を上書きする必要があります。

（gem の上書きと聞くと難しいイメージがあるかもしれませんが、すごくシンプルです！）

```models/user.rb
# devise
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

# validation (name, status を必須にしています。)
  with_options presence: true do
    validates :name
    validates :status
  end

# enum にてユーザーステータスを実装
  enum status: {
    normal: 0,    # ”通常”　ステータス
    withdrawn: 1, # "退会済み”　ステータス
  }

+ def active_for_authentication?
+   super && (status == 'normal')
+ end
end
```

`active_for_authentication?` は、`status == 'normal'` が `true` だった場合のみ、`true` を返します。
つまり、ユーザーが退会済みで `status == 'witdrawn'` である場合は、`false` を返します。

このメソッドを、devise にて作成されているであろう SessionsController の create アクションを呼び出す前に実行するようにします。


```controllers/user/sessions_controller.rb
# frozen_string_literal: true

class User::SessionsController < Devise::SessionsController
before_action :reject_invalid_user, only: [:create]

  protected

  def reject_invalid_user
    user = User.find_by(email: params[:user][:email])
    return unless user # 早期リターン

    return if user.valid_password?(params[:user][:password]) && user.active_for_authentication?

    alert_message = if user.status == 'withdrawn'
                      'あなたはすでに退会済みです'
                    else
                      # ユーザーステータスが normal でも withdrawn でもない場合のエラーメッセージ
                      # 今回は説明のため定義していないですが、運営側からユーザーを凍結する機能がある場合は”あなたのアカウントはサービス規則違反のため凍結されています”などの文章が良いと思います。
                    end
    redirect_to request.referer, alert: alert_message
  end
end
```

ここで定義しているメソッド、 `reject_invalid_user` に関して一つずつ確認していきます。

まず、最初に、 `User.find_by(email: params[:user][:email]) ` にて、ユーザーより入力されたメールアドレスをもとに user を探しています。このタイミングで見つからない場合（＝　`find_by` メソッドが `nil` を返した場合）は、そもそも、入力されたメールアドレスが間違っていると定義できるので、早期リターンを実施しています。

その次に、下記内容について判定しています。
- 入力されたパスワードが正しいか？
- 先ほど実装した、`active_for_authentication?` が `true` を返すか？

ここまで、 return されなかったということは、
「入力されたメールアドレスとパスワードは正しいが、`status` が `'withdrawn'` である」 
と言えるので、エラーメッセージを表示するようにしています。


以上で、「退会済みユーザーをログイン不可」にする実装が完了しました！！！！


# おわりに
ここまでお読み頂きまして、ありがとうございました！
私自身、本記事の内容に関してなかなか解決できず、devise の公式Documents（下記リンク）を読みながら実装することができました。

この記事が誰かのためになれば嬉しいです！！！

# 参考
[devise gem] 公式Documents Authenticatable に関して
https://rubydoc.info/github/plataformatec/devise/Devise/Models/Authenticatable