【Rails】ウィザード形式でのユーザー新規登録機能実装

## 最初に
ユーザー新規登録の際にdevise認証機能を使用して、ユーザーの情報（ユーザーモデル）と、ユーザーの連絡先の情報（addressモデル）を登録できるよにすることが目的であり、1ページでまとめて（fileds_forを使用しない)ではなくウィザード形式を用いて2ページに分割しての実装が難しかったため、備忘録として残します。

## ウィザード形式とは
画面が切り替わりながら操作が進んでいくことで、具体的にはこんな感じ。
https://gyazo.com/c0b7e9d98fb66ad7da724a95a77e4438
ユーザー情報（メールアドレスなど）の登録が完了した際に次のページでユーザー情報に付随する住所などの情報をページを切り替えて表示して登録させている。これをウィザード形式と呼ぶ。らしい。

## 実装の流れ
ユーザー情報を入力させ、その情報を一旦sessionに保持させる。
次に住所の情報を入力させ、入力が完了したらsessionに保持したユーザー情報と住所の情報を各テーブルに保存させる。
ページが切り替わる際にバリデーションをかけられるため、各ページごとにテーブルを分ける必要があります。
例えばユーザー情報登録画面にaddressモデルの住所を登録する項目（カラム）があった場合、userモデルではpermitされていないため、登録が進まないことになってしまう。1ページに1モデル対応させるようにする。

## 手順

1.deviseのコントローラーをカスタムできるようにする。
deviseのデフォルトの状態だとコントローラを作成していなくてもdevise/registrationsコントローラーが起動して、view/regstrations/new.html.hamlにルーティングが敷かれている。（ルーティングはdevise_for: users)
これだとカスタムができないため、対応するコントローラーを作成する。

```
$  rails g devise:controllers users
```
これでusers/registrations_contoroller.rbが作成される。
https://gyazo.com/a8052ca6b6cc4bd1e19ff4e2465cff1b

2.deviseのルーティングを変更
deviseのルーティングを指定する。
<img width="364" alt="スクリーンショット 2020-05-22 1.00.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610541/2b8de680-e1d5-c840-a5bd-eea00f6c92ff.png">

この記述でdeviseのルーティングの指定が完了。
devise/registrationsコントローラー　→　users/registrationsコントローラー
にルーティングを変更することができた。
<img width="960" alt="スクリーンショット 2020-05-22 1.13.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610541/93f1bf50-6c35-9424-ae5d-c88df33b67e0.png">

3.app/controllers/application_controller.rbの修正
deviseのpermitはapplication_controller.rbで行う。

~~~application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday]) 
  end
~~~

~~~
 before_action :configure_permitted_parameters, if: :devise_controller?
~~~
この記述でdevise_controllerが動いた際に、後に記述したconfigure_permitted_parametersメソッドが始動する様にしている。

~~~
def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana, :birthday]) 
  end
~~~

この記述により、Strong Parametersで追加したいカラムを追加。
デフォルトで:email、：password は許可されているので記載しなくてOK。


4.コントローラーの記述の変更
<img width="1440" alt="スクリーンショット 2020-05-22 1.35.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610541/d779c458-8775-3cd8-ff57-e97ee9b9b0d9.png">

先ほど作ったコントローラー。
クラス名意外消してOK。
あとはいつも通り、コントローラーに記載をしていく。

~~~users/registrations_controller.rb
def new
    @user = User.new
  end
~~~

空のハッシュを生成して、

~~~users/registrations_controller.rb
def new
    @user = User.new
  end

 def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.addresses.build
    render :new_address
  end
~~~

createアクションで実際にsessionsに一度保持して、address情報を登録するページに飛ぶように記載している。
最初これ何やってるか意味がわからなかったので3つに分けます。

・1ページ目(ユーザー情報）で入力した情報のバリデーションのチェック
・1ページ目で入力した情報をsessionに保持させること
・次ページの住所情報登録で使用するインスタンスを生成、次ページへ遷移すること

~~~users/registrations_controller.rb
 @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
~~~

Userモデルのインスタンスを生成し、ユーザー情報登録画面から送られてきたパラメータをインスタンス変数@userに代入し、引数としてapplication_controller.rbで修正したsign_up_paramsを引数とする。
configure_permitted_parametersメソッドなのにsign_up_paramsで引っ張ってこれている。これは謎です。
もしかしたらdeviseが勝手に動いてくれているのかも。
インスタンス変数@userに対してvalid?メソッドを適用することで送られてきたパラメータが指定されたバリデーションに引っかかっているかどうかをチェック。falseになった場合は、エラーメッセージとともにnewアクションへrenderさせるような記述となっています。

~~~users/registrations_controller.rb
 @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
 　　session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
~~~

end以下の２文でユーザー情報登録画面での情報をsessionに保持させています。
ウィザード形式上、次ページの入力が完了した段階で各モデルのデータをDBに反映させたいです。なのでここでsessionに保持させる必要があります。この２行ずば抜けて意味不明だったので細かく書きます。。（笑）
session["devise.regist_data"]に値を代入します。この時、sessionにハッシュの形で情報を保持させるために、attributesメソッドを使用して、@userの属性を全てデータとして整形しています。
ここで注意したいのがパスワードはattributesメソッドでは追加できないということ。
paramsから取得して別で追加してあげます。パスワードを再度sessionに代入する必要があり、session["devise.regist_data"][:user]["password"]に再代入している。らしい。全然わからん。（泣）

~~~users/registrations_controller.rb
def new
    @user = User.new
  end

 def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.addresses.build
    render :new_address
  end
~~~

最後の２行で、次ページでユーザー情報に紐ずく住所情報を入力させるため、addresses.buildで生成したインスタンス@userに紐づくAddressモデルのインスタンスを生成して@addressに代入。
住所情報を登録させるページを表示するためnew_addressアクションのビューへrenderしています。

5.new_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
end
~~~

devise_scope :user do~endでルーティングを指定。これで#new_addressと#create_addressが使用できるようになる。
対応するnew_address.html.hamlファイルは、
app/views/devise/registrations/下に移動。これでusers/registrationsのアクションでビューが表示される。

6.create_addressアクションでユーザー情報とプロフィール情報をテーブルに保存。

~~~registrations_controller.rb
def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.addresses.build(@address.attributes)
    @user.save
    session["devise.regist_data"]["user"].clear
    sign_in(:user, @user)
    redirect_to root_path
end

private
  def address_params
    params.require(:address).permit(:post_code, :prefectures, :city, :block, :building, :phone_number)  
  end 
~~~

さきほどsessionにuserオブジェクトのデータを保存したので、create_addressアクションでもuserオブジェクトを生成して@userに代入。2行目でprivateメソッド下の引数で address_paramsメソッドを引数としてAddressモデルを生成、@addressに代入。
あとは先ほどと同じで、addressオブジェクトのバリデーションを確認して、通ったら保存。
addressesテーブルに外部キーを設定していると、バリデーションに引っかかるので、モデルでoptional: trueを追加。

~~~address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true
end
~~~
optional: trueは外部キーにnullが入ることを許可してくれるオプション。これでバリデーションに引っ掛からなくなる。
その後はuserモデルに紐付いたaddressモデルという記述を追加。
@userをsaveして、sessionをclearメソッドを使用して削除。
まだ登録できただけなので、sign_inメソッドでサインイン。

あとはcreate_address.html.hamlが動くかを確認して、正常に動けばウィザード形式の実装完了です！

##参考文献

・https://satoryu.hatenablog.com/entry/antipattern_in_designing_models_for_wizard
・https://note.com/vixer93/n/nac92cc4c0983