【ruby on rails】「devise」gemを利用したウィザード方式のユーザー登録

<br>**Toshi**です！！
今日はウィザード方式でのユーザー情報の登録についてまとめます。


#　この記事の対象の方
>・Ruby on railsを学んでいる方<br>・deviseのgemの使い方がわかる方

ただ、条件関係なくその他の方も全然Welcomeです:sunglasses:


#　「ウィザード形成」とは

>**ウィザード形式**とは、対話するように順番に操作が進んでいく方式のことです。例として、ユーザー登録の際に、最初のページでユーザー情報（名前やメールアドレスなど）の登録を行い、次のページで住所などの情報を登録するように、ページを切り替えて登録を行うことが挙げられます。

<br>ユーザーのメリットとして
>1ページで縦長に全ての情報を登録するよりも、どの情報を、どのページで登録しているのか分かるので、ユーザーからすると見やすく使いやすいという特徴があります。

#　STEP０ 事前に準備できていること

・deviseのgemを、ruby on railsにインストールしていること<br>
（すでにできているアプリを前提に説明をしていきます）
・ビューが完成し、ユーザー登録はできている状態。

#　STEP1 ウィザード形式のイメージを掴む
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759485/b81f70e3-e6c6-acae-fe90-bff3bbde2fc0.png)

画像は完全拝借物ですが、上記のようなイメージです。


#　STEP2 「ウィザード形式で次の画面で登録するための情報用のモデルを作成する」
今回はユーザー情報に加えて、本人の住所情報を登録させます。

>今回は「Addressモデル」という名前で、ユーザー情報登録後に登録する住所情報のモデルを作成しています。

⭐️重要ポイント①
**最初のユーザー情報登録画面画面で、「user_id」が外部キーの設定によって作られる形ですが、addressesテーブルには、住所情報入力時点では登録されない！**

そのため、下記のように住所の登録時にuser_id(Addressesテーブルの外部キー）が、nil（空欄）でも登録を許可するという設定を、モデルに追加します。

```app/models/address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true　#⭐️ここがポイント！！
  validates :postal_code, :address ,presence: true
end
```

<br>次にユーザー情報と住所情報を１：１の関係なのでアソシエーションを設定します。

```app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :name, :age ,presence: true
  has_one :address　#追加
end
```

<br>⭐️重要ポイント②
**ルーティングにて、Userモデルのコントローラーとの紐付けの設定を追加する**
まず、deviseのコントローラーを作成します。作成後、「rails routes」を実行すると以下のようになります。**

```ruby:ターミナル
% rails g devise:controllers users
```
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759485/3c0f211f-8ef4-d252-226c-08d3ceb6dd88.png)
devise管理下にusersコントローラーを作成されていることがわかります。

<br>
しかし、deviseはgemをインストールしてしまえば、ビューファイルやモデルを作ったりなどでユーザー情報管理ができてしまう反面、gemの中でほぼ完結してしまい、ウィザード形式のように追加実装がやりにくいデメリットもあります。<br><br>そこで、作成したコントローラーに紐付けの設定を行い、そこでcreateやnewといった7つのアクションを記載し実施できるようにします。ルーティングを以下のように設定します。

```config/routes.rb
Rails.application.routes.draw do　　#以下を変更
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }　#ここまで
  root to: "home#index"　#すでに作成しているコントローラーに関するルーティングです。
end
```
<br>
rails routesを再度実行すると紐づくコントローラーが変更されています。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759485/f3c1929e-ff21-4ae5-52df-a13704a06514.png)
これで、users配下のコントローラーに7つのアクションを記載し処理を実装することができます。


#　STEP3 「users配下のコントローラーに、ユーザー情報登録のアクションを記載する」

ユーザー新規登録ページへ遷移するnewアクションを設定します。

```app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
 # before_action :configure_sign_up_params, only: [:create]
 # before_action :configure_account_update_params, only: [:update]

  def new
    @user = User.new
  end

# 中略

end
```
Userモデルの新規インスタンスを生成しました。new.html.erbを下記のように編集します。（ビューファイルはデフォルトから変えていません）

```app/views/devise/registrations/new.html.erb
<h2>ユーザー情報登録</h2>

<%= form_for(@user, url: user_registration_path) do |f| %>　#変更
  <%= render "devise/shared/error_messages", resource: @user %>　#変更

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <div class="field">
    <%= f.label :age %><br />
    <%= f.text_field :age %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Next" %>　#変更
  </div>
<% end %>

<%= render "devise/shared/links" %>
```
※実際作る人はここで画面の確認を。

<br>次に保存のアクションを記載していきます。

```app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
 # before_action :configure_sign_up_params, only: [:create]
 # before_action :configure_account_update_params, only: [:update]

 def new
   @user = User.new
 end

 def create　#追加
   @user = User.new(sign_up_params)
    unless @user.valid?
      render :new and return
    end
   session["devise.regist_data"] = {user: @user.attributes}
   session["devise.regist_data"][:user]["password"] = params[:user][:password]
   @address = @user.build_address
   render :new_address
 end　＃ここまで

 # 省略

end
```

⭐️重要ポイント③
**画面遷移をしても情報を保持するために「session」を使う**

最後のページまで遷移した後に保存させる為に、sessionを用います。

1ページ目で入力した情報のバリデーションチェックが完了したらsession["devise.regist_data"]に値を代入します。この時、sessionにハッシュオブジェクトの形で情報を保持させるために、attributesメソッドを用いてデータを整形しています。

なお、[devise.regist_data]は変数ではありますが、deviseが落ちてしまった時に自動で保持する値を削除できるといった副次的なメリットもあります。

また、paramsの中にはパスワードの情報は含まれていますが、attributesメソッドでデータ整形をした際にはパスワードの情報は含まれません。そこで、パスワードを再度sessionに代入する必要があります。これをsession["devise.regist_data"][:user]["password"]が担い、sessionに追加しています。


最後の「render :new_address」で次の画面の遷移をできるようにしています。new_addressはまだ未作成のためここで保存をするとエラーになります。

<br>
#　STEP4 「住所情報を登録する、ウィザード形式の完成」

住所情報の登録のために先ほど、new_addressのビューに飛ぶ設定(renderメソッド）を追加しました。その画面で登録ページを表示させる「new_address」アクションと、登録を行う「create_address」アクションをルーティングに設定していきます。

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do #追加
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end #ここまで
  root to: "home#index"
end
```
<br>
次に Viewを作成していきます。今回は、借り物のViewファイルを使います。

```app/views/devise/registrations/new_address.html.erb

<h2>住所情報登録</h2>

<%= form_for @address do |f| %>
  <%= render "devise/shared/error_messages", resource: @address %>

  <div class="field">
    <%= f.label :postal_code %><br />
    <%= f.text_field :postal_code %>
  </div>

  <div class="field">
    <%= f.label :address %><br />
    <%= f.text_field :address %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```
ここまでで住所登録までの画面遷移はできますが、まだ登録はできません。<br>住所情報を保存するために、コントローラーの設定を追加します。<br><br>

⭐️重要ポイント④
**ウィザード形式で遷移後の画面であっても、「registrations_controller.rb」（作成したコントローラーファイル）にアクションを記載していく。**
<br>
create_addressアクション内でやることは、以下4点です。

>**・2ページ目で入力した住所情報のバリデーションチェック**
**・バリデーションチェックが完了した情報とsessionで保持していた情報を合わせ、ユーザー情報として保存**
**・sessionを削除する**
**・ログインをする**


new_addressはビューファイルを用意し、特に情報を遷移時に表示させる必要はないのでコントローラーに記載は入りません。「registrations_controller.rb」に、「create_address」を以下のように追記していきます。

```app/controllers/users/registrations_controller.rb

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

 def new
   @user = User.new
 end

 def create
   @user = User.new(sign_up_params)
    unless @user.valid?
      render :new and return
    end
   session["devise.regist_data"] = {user: @user.attributes}
   session["devise.regist_data"][:user]["password"] = params[:user][:password]
   @address = @user.build_address
   render :new_address
 end

 def create_address #追加
   @user = User.new(session["devise.regist_data"]["user"])
   @address = Address.new(address_params)
    unless @address.valid?
      render :new_address and return
    end
   @user.build_address(@address.attributes)
   @user.save
   session["devise.regist_data"]["user"].clear
   sign_in(:user, @user)
 end

 private

 def address_params
   params.require(:address).permit(:postal_code, :address)
 end　#ここまで

# 省略

end 
```

上記の4つがどのように設定されているのか。
>**・2ページ目で入力した住所情報のバリデーションチェック**

```
 @user = User.new(session["devise.regist_data"]["user"])
 @address = Address.new(address_params)
    unless @address.valid?
      render :new_address and return
    end
```

valid?メソッドを用いて、address.rbファイルに記述したバリデーションを実行します。この際、アプリケーションが設定したバリデーションの条件を満たしている場合はtrueを返します。その逆に、バリデーションの条件を満たしていない場合はfalseを返します。今回はunlessを用いているため、falseの場合にunless内に記述した処理が実行されます。

render :new_addressと記述しているため、設定したバリデーションの条件を満たさない場合は、renderメソッドによってnew_addressアクションに画面遷移します。

このとき、and returnと記述することで処理を中断させています。もしこのand returnがない場合はunless以降の記述も実行され、ユーザーの情報は保存されてしまいます。これを防ぐためにand returnを記述して処理を中断しています。

>**・バリデーションチェックが完了した情報とsessionで保持していた情報を合わせ、ユーザー情報として保存**

```
@user.build_address(@address.attributes)
@user.save
```

同コントローラーにある「create」アクションにて、「@address」の変数を@user.build_addressと、@user変数のbuild_address要素としてあとで格納できるようにしています。
現在セッションの情報が格納されている@userに対して、「build_address」の要素を使い代入する形で住所情報を格納しています。saveメソッドを用いてテーブルに保存します。

>**・sessionを削除する**

```
session["devise.regist_data"]["user"].clear
```

「.clear」メソッドを用いて、sessionの情報を削除します。

>**・ログインをする**

```
sign_in(:user, @user)
```

情報が保存できてもログインできているわけではないので、sign_inメソッドでログインをします。


create_address.html.erbのビューファイルを作成し完成です

```app/views/devise/registrations/create_address.html.erb
<h2>登録が完了しました</h2>
<%= link_to "トップへ戻る", root_path%>
```
![4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/759485/9924fffc-7275-fff4-85f7-082a1164e5f8.gif)


#最後に

最初はどうしても拝借気味にはなりますが、自分なりの理解している内容などを盛り込むのも大事ですね。引き続きよろしくお願いします！！