【Rails】deviseを使ってパスワード再設定機能を実装（開発環境ver）

# 環境
macOS Big Sur Ver11.2.1
Rails6.1.1
Ruby 2.6.5

# やりたいこと
deviseを導入しているので、パスワードリセット機能を実装してみる。
流れとしては、
①ログイン画面からパスワードリセットメールを送るためのアドレス入力画面に遷移し、
②入力したアドレスにパスワードリセットのためのURLが掲載されたメールが届き、
③URLをクリックするとパスワードリセット画面に遷移する。
④新しいパスワードを入力するとパスワードの変更ができる。
をイメージしてます。

# モデルの実装
devise導入後、「rails g devise user」コマンドでUserモデルを生成している。
パスワードリセットに使うのは、deviseのモジュールの中の「recoverable」

```ruby:app/models/user.rb
class User < ApplicationRecord
(略)
  devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
（略）
end
```

# ルーティングの確認
デフォルトのルーティングが使えるので、「rails routes」で確認します。
コントローラーとアクション名の組み合わせが、「devise/passwords#アクション名」になっている部分が使用するルーティングです。

# ビューファイルの生成
「rails g devise:views」コマンドでデフォルトのビューを生成します。今回は、
①パスワードリセットメールの送信先アドレスを入力させるビュー(new.html.erb)
②リセットメール記載のURLをクリックして表示されるパスワード再設定画面のビュー(edit.html.erb)
の２つが必要。
それぞれデフォルトのビューを以下のように変更。

```html:app/views/devise/passwords/new.html.erb
<%= render "shared/second-header"%>

<%= form_with model: @user, url: user_password_path, class: 'registration-main', local: true do |f| %>
<div class='form-wrap'>
  <div class='form-header'>
    <h1 class='form-header-text'>
      パスワード再設定
    </h1>
  </div>

  <%# エラーメッセージの出力 %>
  <%= render 'shared/error_messages', model: f.object %>

  <div class="form-group">
    <div class='form-text-wrap'>
      <label class="form-text">メールアドレス</label>
      <span class="indispensable">必須</span>
    </div>
    <%= f.email_field :email, class:"input-default", id:"email", placeholder:"PC・携帯どちらでも可", autofocus: true %>
  </div>
  <div class="form-group">
    <h2 class='form-bottom-text'>
      入力されたメールアドレスにパスワード再設定メールを<br>送信します
    </h2>
  </div>
  <div class='register-btn'>
    <%= f.submit "会員登録" ,class:"register-red-btn" %>
  </div>
</div>
<% end %>

<%= render "shared/second-footer"%>
```

こちらは普通の入力フォームを生成すればOK。

```html:app/views/devise/passwords/edit.html.erb
<%= render "shared/second-header"%>

<%= form_with model: @user, url: user_password_path, method: :patch, class: 'registration-main', local: true do |f| %>
<div class='form-wrap'>
  <div class='form-header'>
    <h1 class='form-header-text'>
      パスワード再設定
    </h1>
  </div>

  <%# エラーメッセージの出力 %>
  <%= render 'shared/error_messages', model: f.object %>

  <div class="form-group">
    <div class='form-text-wrap'>
      <label class="form-text">新しいパスワード</label>
      <span class="indispensable">必須</span>
    </div>
    <%= f.password_field :password, class:"input-default", id:"password", placeholder:"6文字以上の半角英数字" %>
    <p class='info-text'>※英字と数字の両方を含めて設定してください</p>
  </div>
  <div class="form-group">
    <div class='form-text-wrap'>
      <label class="form-text">パスワード(確認)</label>
      <span class="indispensable">必須</span>
    </div>
    <%= f.password_field :password_confirmation, class:"input-default", id:"password-confirmation", placeholder:"同じパスワードを入力して下さい" %>
  </div>

  <%# 隠しフィールドでトークンもパラメーターで送る。こうすることでDBに保存されているリセットトークンとここから送られたトークンが一致するか確認している。 %>
  <%= f.hidden_field :reset_password_token %>

  <div class="form-group">
    <h2 class='form-bottom-text'>
      よろしければ以下のボタンを押してください
    </h2>
  </div>
  <div class='register-btn'>
    <%= f.submit "パスワードを変更する" ,class:"register-red-btn" %>
  </div>
</div>
<% end %>

<%= render "shared/second-footer"%>
```

パスワード再設定画面では、「hidden_field」タグがキモ。
この画面のURLに含まれるパスワードリセットトークンの値と、パスワード再設定メールを送信した時にデータベースに保存されている（deviseが自動で保存してくれる）トークンの値が等しいか判断するのに必要。
隠しフィールドでトークンも送信してあげないと、deviseのコントローラーでトークンの一致を確認できず、「トークンを入力してください」というエラーが出てしまう。

# 送信メールの内容変更
実際に送信されるメールの文章を編集する。
※本当はユーザー名を表示したりしたかったが、なぜか文字化けしてしまったのでルーティングのアドレスだけ変更。

```html:app/views/devise/mailer/reset_password_instructions.html.erb
<p>Hello <%= @resource.email %>!</p>

<p>Someone has requested a link to change your password. You can do this through the link below.</p>

# この下のルーティングだけ編集した。
<p><%= link_to 'Change my password', edit_user_password_url(@resource, reset_password_token: @token) %></p>

<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
```

# 環境設定
今回は開発環境でメールが送れるかチェックしたいので、以下のファイルを編集。
ここの記述はRailsチュートリアルの11章「アカウントの有効化」も参考にしました。

```ruby:config/environment/development.rb
(略)
# Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  # 開発環境でメールを送るためのホストの設定（Railsチュートリアル11章）
  host = 'localhost:3000'
  config.action_mailer.default_url_options = { host: host, protocol: 'http' }
（略）
```

# 補足
deviseでパスワード再設定を実装するとデフォルトで以下の機能が実装されているので、Railsチュートリアルでイチから実装した内容が結構含まれています。
①パスワード再設定リンクの有効時間（デフォルトは送信されてから６時間）→config/initializers/devise.rb
②リセットトークンの消去（パスワード再設定に成功したら、他人が不正利用できないようトークンを削除）

# 最後に
deviseを使ったパスワード再設定のやり方はほとんど情報がなかったので苦労しました。
今後、本番環境でもパスワードリセットができるよう追加実装していきます。