【Rails】deviseの会員登録に確認画面と完了画面を加える。


前書き

※この記事は、以前はパラメータを渡す方式(GET)で実装しましたが、POSTに変更しました。

環境

Ruby 2.5.7
Rails 5.2.4

gem

gem 'devise'

前提

deviseの新規登録フォームがカスタムできる状態(デフォルトではない状態)
※事前に、新規登録フォームが自由にカスタムできることをご確認ください。

やりたいこと

deviseのデフォルト設定ではsign_upページで情報が入力され送信されると、そのまま即登録され、あらかじめ指定した(もしくはデフォルトの)ページに遷移します。
登録の前に入力された情報が確認できるページを挟んだり、登録が完了したことをお知らせするページがあると、より親切かと思いますので、今回は「入力フォーム」に加えて、「入力された情報の確認画面」、「登録完了をお知らせする画面」を作成していきます。
今回の作成で完成するページの流れとしては、
新規会員情報の入力フォームregistrations/new.html.erb(既存)→確認画面registrations/confirm.html.erb(新規作成)→完了画面registrations/complete.html.erb(新規作成)という順番で画面が遷移していきます。

手順

1.views(views/users/registrations)に入力画面(new.html.erb)、確認画面(confirm.html.erb)、完了画面(complete.html.erb)を用意
2.routes.rbに、1.で追加したviewファイルのルーティング、registrations_controller.rbの使用を追記
3.controllers(controllers/users/registrations_controller.rb)でconfirmアクション、completeアクションを追記、createアクションの再定義、登録後のリダイレクト先の変更

1.入力画面、確認画面、完了画面の作成

冒頭で記した通り、新規登録時の画面の流れとしては、
新規登録入力フォームnew.html.erb→確認画面confirm.html.erb→完了画面complete.html.erbとなります。
確認画面で"登録する"ボタンを押すとdeviseの標準機能でユーザー情報が登録されます。

入力画面

new.html.erb
<div>

  <h2>新規会員登録</h2>

  <%= form_with model: @user, url: users_sign_up_confirm_path(@user), local: true do |f| %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.label :phone_number %>
    <%= f.text_field :phone_number %>

    <%= f.label :email %>
    <%= f.email_field :email %>

    <%= f.label :password %>
    <% if @minimum_password_length %>
      (<%= @minimum_password_length %>文字以上)
    <% end %>

    <%= f.password_field :password %>
    <%= f.password_field :password_confirmation %>

    <%= render "users/shared/links" %>

    <%= f.submit "確認" %>
  <% end %>
</div>

元々用意されているregistrations/new.html.erbのフォームを書き換えていきます。
登録情報に名前や電話番号情報を追加する方法は割愛します。
追加される場合は先にそちらの設定をして動作が確認できた上で、今回の方法を試していただくか、カラムは増やさずにデフォルトの設定(メールアドレス、パスワードのみ)でお試しください。

ポイントとしてはフォームタグです。

registrations/new.html.erb

  ...

  <%= form_with model: @user, url: users_sign_up_confirm_path(@user), local: true do |f| %>

    ...

  <% end %>

ここで入力された値が@userに保存され、確認画面に渡されます。
後述しますが、確認画面はPOSTメソッドですので、送信ボタンが押されると後述するコントローラー(registrations_controller.rb)で指定したURL(アクション)が実行され、確認画面に遷移します。(※URLやパスはご自身の環境によって異なります。)

今回使うフォームタグはform_withです。
form_withはデフォルトの送信方法がAjax(remote: true)で、画面遷移が行われない仕様なので、local: trueも明示する必要があります。
form_forの場合はデフォルトがlocal: trueなので、普段は意識する必要はありません。

確認画面

registrations/confirm.html.erb
<div>

  <h2>入力情報の確認</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

    <p><%= f.label :name %><%= f.text_field :name, value: @user.name, readonly: true %></p>
    <p><%= f.label :phone_number %><%= f.text_field :phone_number, value: @user.phone_number, readonly: true %></p>
    <p><%= f.label :email %><%= f.text_field :email, value: @user.email, readonly: true %></p>
    <p><%= f.label :password %><%= @password %></p>

    <%= f.submit "修正する", name: :back %>
    <%= f.submit "登録する" %>

  <% end %>
</div>

確認画面では[登録するボタン]が押された時にdeviseの機能を使って会員登録を行いたいので、フォームタグはdeviseで元から用意されているform_forresourceなど、そのまま利用します。

registrations/confirm.html.erb
...
  <p><%= f.label :name %><%= f.text_field :name, value: @user.name, readonly: true %></p>
...

f.text_fieldを利用していますが、オプションとしてreadonly: trueを付けているため、フォーム内の編集はできず表示だけとなります。
また、valueオプションで前のページから受け取った@userをそのまま値にしていますので、[登録するボタン]が押されると、中の値がそのまま送信されます。

<p><%= @password %></p>はパスワードにブラインドをかけてパスワードの文字数だけが確認できるようにしています。
ブラインドのやり方についてはコントローラーのところで解説します。

<%= f.submit "修正する", name: :back %>
submitが押されると、指定したパスのアクション(今回の場合はregistrations_controller.rbのcreateアクション)が実行されますが、:backキーが付与された状態でcreateが行われた場合は、保存されずにrender :newが行われるようにコントローラーで記述しています。(後述します。)

完了画面

users/complete.html.erb
<div>
  <p>登録が完了しました!</p>
</div>

前のページで登録するが押されると、確認した会員情報が保存されると同時に、この画面に遷移します。

2.新たに作成したviewsとusers/registrations_controller.rbをroutes.rbに記述する

routes.rb
Rails.application.routes.draw do

  ...

  # registrations_controller.rbを有効にします。
  devise_for :users, controllers: {
    ...
    registrations: 'users/registrations'
  }

  # views/users/registrations内に作成したconfirm.html.erbとcomplete.html.erbもルーティングに追加します。
  devise_scope :user do
    post 'users/sign_up/confirm', to: 'users/registrations#confirm'
    get 'users/sign_up/complete', to: 'users/registrations#complete'
  end

  ...

end

3.registrations_controller.rbにconfirmとcompleteを追加

確認画面・完了画面のレンダリングのために両アクションの追加、createアクションの再定義、登録完了後のリダイレクト先の変更を行います。

users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController

  ...

  # newアクションは変更しません。
  # GET /resource/sign_up
  # def new
  #   super
  # end

  # createアクションはコメントアウトを外し、superの上に下記を追加
  # POST /resource
  def create
    @user = User.new(sign_up_params)
    render :new and return if params[:back]
    super
  end

  ...

  # 新規追加
  def confirm
    i = 0
    @password = ""
    while i < @user.password.length
      @password += "*"
      i += 1
    end
  end

  # 新規追加
  def complete
  end

  # アカウント登録後
  def after_sign_up_path_for(resource)
    users_sign_up_complete_path(resource)
  end

  ...

end

createアクション

@userは入力画面で入力された値がそのまま引き継がれています。(確認画面では編集されないため。)

render :new and return if params[:back]
この一文は以下のように書き換えることもできます。

users/registrations_controller.rb
if params[:back]
  render :new
  return
end

super

submitが押された時に:back([修正するボタン])キーが含まれていたらrender :newを実行し、@userを保持したまま入力画面に戻ります。
returnを記述することで、レンダリングがされた後にcreateアクションが終了します。returnを書き忘れるとそのままsuperが実行されてしまうため、修正するが押されたにもかかわらず保存されてしまい、コンプリート画面に遷移します。
また、superDevise::RegistrationsControllerでの設定をそのまま継承するという意味で、この記述がなければform_forの中にあるresourceresource_nameも使えなくなるので、createアクションのコメントアウトを外すときは消さないように注意しましょう。

confirmアクション

@user.password.lengthでパスワードの文字数を取得しています。
while i < @user.password.length ... endで処理の中でiが1ずつカウントアップしていきます。
同時に、直前で定義した変数@password = ""にも"*"が足されていき、文字数に達したら処理が終了します。
そしてviewに<%= @password %>変数の中身を表示します。

completeアクション

レンダリングのためだけのアクションなので、中身は空白のままで大丈夫です。(暗示的にrender :completeが実行されます。)

登録後のリダイレクト先

確認画面で登録するが押されたときに、完了画面registrations/complete.html.erbに遷移させる必要があります。
デフォルトか、もしくは任意で別のページがリダイレクト先になっているかと思いますが、users_sign_up_complete_path(resource)(完了画面のpath)に設定します。

まとめ

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

最後まで読んでいただきありがとうございました。

参考サイト

より実用的な使い方については、私のGitHubに実際に使っているファイルを公開しているのでそちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi