Railsでウィザードフォームを作ってみた

#はじめに
閲覧いただきありがとうございます。
よわよわエンジニアです。

僕みたいなよわよわエンジニアでもわかるように記載していきたいです。
##今回のテーマと環境
Railsでウィザードフォームを実装しました。

Rails 5.2.4
Ruby 2.5.1
gem 'devise'

デバイスのインストールとモデル作成まではいってると仮定します。

##ウィザードフォームとは？
入力項目が多い際にページ遷移しながら
１ページごとに入力された情報を保持して、全ての入力が終わってから最後にその情報を送信し、
DBに保存するフォームのこと。

要するに入力項目が100個あったとして、
１ページで１００個入力するよりも、1ページ１０個x１０ページ作っちゃおうよというものです。

↓例えばこういうの
<img width="1676" alt="スクリーンショット 2019-12-13 18.25.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550679/8604a1f5-aeeb-1eda-a119-52365704f0f9.png">

パッとみた時に
入力項目が限られているので認識しやすいが、何ページもあって萎えちゃうっていうデメリットもある。

###ウィザードの動きの原理

通常なら１ページに入力したフォームの内容は、２ページ目に**a**や**link_to**でページ遷移すると、
入力内容が消えてしまいますが

**session**という物を使い、入力内容を保持したままで
_form_の_submit_を押すことでページ遷移し、全ての入力が終わった段階で_create_を走らせてDBに内容を保存します。

##いざ実装

###デバイスとは関係ないコントローラーを作る
まずはコントローラーを作ります。
signupなど、わかりやすい名前で作っちゃいましょう。

    $rails g controller signup

###ルーティングの設定

前述のとおり今回実装するウィザードは、
**session**といもので情報を保持し、次のページへ進まなければいけません。

今回は`collection do`で必要な数だけページのルーティングをしましょう。
   
```routes.rb
resources :signup do
    collection do
      get 'page1'
      get 'page2'
      get 'page3' # ここで全て入力完了。このページの送信ボタンを押した後にcreateが走る
      get 'done' # 登録完了後のページ
    end
  end
```

###フォームを作る

各ページ入力したいフォームを作ります。
この時に、先ほど設定したルーティングの数だけページを新規作成しましょう。
その際のURLの**送信先に注意！！**

```page1.html.haml
= form_for @user, url: page2_signup_path, method: :get, html: {class: 'form__box'} do |f|
  = f.text_field :nickname, placeholder: '例) メルカリ太郎'
  = f.email_field :email, placeholder: 'PC・携帯どちらでも可'
  = f.password_field :password, placeholder: '6文字以上'
  = f.password_field :password_confirmation, placeholder: '6文字以上'
  = f.submit #これでpage2へ進む。link_toなどで遷移すると情報が消えちゃうので注意 
```

```page2.html.haml
= form_for @user, url: page3_signup_path, method: :get, html: {class: 'form__box'} do |f|
  = f.text_field :last_name, placeholder: '例）鬼頭'
  = f.text_field :first_name, placeholder: '例）権蔵'
  = f.text_field :last_name_kana, placeholder: '例）オニガシラ'
  = f.text_field :first_name_kana, placeholder: '例）ゴンゾウ'
  = f.submit #これでpage3へ進む。link_toなどで遷移すると情報が消えちゃうので注意 
```

**form_for @モデル名, url: 次のページのアクションが動くprefix,  do |f|**
と記載してください。（クラス名はお好みで）

###session登場
Railsにはsessionというものがあります。
キーバリュー形式で情報を保管できるよー。ってやつです。
保管方法は
**session[:キー] = 値**で保管できます。以下サンプル。

```ruby
session[:name] = "田中"
#この場合、:name　キーに　value "田中"　を保管できる。
#binding.pryなどで保管できているか確認するときは　session[:name]　と入力すると内容が表示される。
```

このsessionで入力したものを保持しながら、formのsubmitで次のページへ送り、保持した状態でcreateかけると保存できます。

##コントローラーの設定

```signup_controller.rb

class SignupController < ApplicationController
  # 各アクションごとに新規インスタンスを作成します←ここ重要
　# 各アクションごとに、遷移元のページのデータをsessionに保管していきます

  def page1
    @user = User.new # 新規インスタンス作成
  end

  def page2
    # step1で入力された値をsessionに保存
    session[:nickname] = user_params[:nickname]
    session[:email] = user_params[:email]
    session[:password] = user_params[:password]
    session[:password_confirmation] = user_params[:password_confirmation]
    @user = User.new # 新規インスタンス作成
  end

  def page3
    # step2で入力された値をsessionに保存
    session[:last_name] = user_params[:last_name]
    session[:first_name] = user_params[:first_name]
    session[:last_name_kana] = user_params[:last_name_kana]
    session[:first_name_kana] = user_params[:first_name_kana]
    @user = User.new # 新規インスタンス作成
  end

~省略。遷移ページ分繰り返す~

private
　# 受け取り許可するキーを設定します
  def user_params
    params.require(:user).permit(
      :nickname, 
      :email, 
      :password, 
      :password_confirmation, 
      :last_name, 
      :first_name, 
      :last_name_kana, 
      :first_name_kana, 
      ~省略。各pageで入力し、受け取る値を設定した分だけ受け取れるようにしておく~
  )
  end
```

ポイントは、
**page1で入力した情報を、page2アクションが呼び出されるときにsession代入すること。**

`（理由）`

```signup_controller.rb
   x間違い
  def page1
    session[:nickname] = user_params[:nickname]
    session[:email] = user_params[:email]
    session[:password] = user_params[:password]
    session[:password_confirmation] = user_params[:password_confirmation]
    @user = User.new # 新規インスタンス作成
  end
```

上記のようにpage1が呼び出されるアクションで代入してしまうと、
page1を開いた時にはフォームに情報が入力されていないのに「何を代入するんじゃ、userの情報なんでないぞ小僧」と叱られちゃいます。


また、新規新スタンスを作るのを忘れずに。

##createを走らせる方法

    $rails routes

を実行して、createアクションが走るパスを探してください。

```
signup_index GET      /signup(.:format)                  signup#index
             POST     /signup(.:format)                  signup#create
```

こんな感じででてくるかと思われます。
今回ですと、signup_indexのPOSTの横にsignup#createがあるので
パスとしては`signup_index method: :post`ですね。

```page3.html.haml
= form_for @user, url: signup_index_path, method: :post, html: {class: 'form__box'} do |f|
  ~省略~
  = f.submit #これでcreateが走る。 
```

page1,page2との違いはurlとその直後のmethodです。
ここに先ほど探してきたパスを入れていますね。その結果createが動きます。
###createの設定
```singnup_controller.rb
def create
    @user = User.new(
      nickname: session[:nickname], # sessionに保存された値をインスタンスに渡す
      email: session[:email],
      password: session[:password],
      password_confirmation: session[:password_confirmation],
      last_name: session[:last_name], 
      first_name: session[:first_name], 
      last_name_kana: session[:last_name_kana], 
      first_name_kana: session[:first_name_kana], 
      ~省略~
    )
    if @user.save
     # ログインするための情報を保管
      session[:id] = @user.id
     #データベースに保存できた場合どこに飛ぶか？
      redirect_to done_signup_index_path
    else
     #データベースに保存ができなかった場合どこに飛ぶか？
      render '/signup/registration'
    end
  end
```

今まで保持してきた値をインスタンスに渡してあげます。
なので、ここはsessionの数だけ記載が必要です。
これにより、今までsession君が保持していたものをインスタンスに渡し、全てのパラメーターをもったインスタンスが誕生し、クリエイトが走ることにより、DBに登録できるという流れです。

##自動ログイン

```singnup_controller.rb
def done
    sign_in User.find(session[:id]) unless user_signed_in?
end
```

最後に、デバイスのsign_inメソッドに モデル名.findで保存したデータのidを用いてサインインさせます。
これで、doneアクションが呼び出されて自動的にサインインさせることができます。

##さいごに
いかがだったでしょうか？
この通りに設定していけば動くはずです。

ただ、現在の問題点としては、
①１つのモデルでしかフォームを作れない。
②バリデーションエラーでもページ遷移できてしまう。

これに関してはまた近いうちに追記しようと思いますが、
「今知りたいんだよ！このクソ雑魚エンジニアが！」って人は下記記事を参照してもらえればわかりやすいと思います。

■他テーブルの情報も一括保存　＋　バリデーション
https://qiita.com/NT90957869/items/614842934feff9812203

むしろこの記事だけ見てもいいかも・・・・（根も葉もない）



以上、ご覧いただきありがとうございました。