Railsでウィザード形式の入力を行う（wicked）

## 概要
以下のgemを利用してウィザード形式の入力を行う
[GitHub - schneems/wicked: Use wicked to turn your controller into a wizard](https://github.com/schneems/wicked)

ほとんど以下を見ながら作成させてもらいました、ありがとうございます。
[Rails5.1でフォームをウィザード化〜wicked | | Scimpr Blog](https://blog.scimpr.com/2018/02/21/rails5-1%E3%81%A7%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%82%92%E3%82%A6%E3%82%A3%E3%82%B6%E3%83%BC%E3%83%89%E5%8C%96%E3%80%9Cwicked/)

作成したソースは以下に置いております。
[GitHub - mojihige/wicked-example](https://github.com/mojihige/wicked-example)

### 出来る事
入力画面を分割してウィザード形式で登録することを簡単にできる

### 出来ない事
step毎に保存される仕様になっているため
途中のstepで保存しないようには出来ない　※１

## 作成手順
### 1. install
`gemfile` に以下を追加

```ruby
gem 'wicked'
```

### 2. usersモデルを作成
`scaffold`で作成

```
rails generate scaffold user email:string password:string zip:string address:string message:string
```

### 3. userの登録処理を修正
#### controllerの修正
登録後に次のSTEPの入力画面に遷移するため `#create` を以下のように修正

```ruby
  def create
    @user = User.new(user_params)
    if @user.save
      session[:user_id] = @user.id
      redirect_to user_steps_path
    else
      render :new
    end
  end
```

#### 4. Viewの修正
最初に入力する項目を`email`と`password`のみにするため
`_form.html.erb`を以下のように修正

```erb
<%= form_with(model: user, local: true) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email, id: :user_email %>
  </div>

  <div class="field">
    <%= form.label :password %>
    <%= form.text_field :password, id: :user_password %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

### 5. ウィザードを制御するcontrollerを作成
ウィザード入力をコントロールするcontrollerの修正点は以下の２つ

- routesの修正
- 作成したコントローラーの実装

#### UserStepsControllerを作成
以下のコマンドを実行してcontrollerを作成（コントローラ名の制約はありません）

```
rails g controller user_steps
```

作成したcontrollerの内容を以下のように修正
stepsが入力する順番のキーになります。名前は自由に決められます。
`#finish_wizard_path`で登録後の遷移先を変更できます。

```ruby
class UserStepsController < ApplicationController
  include Wicked::Wizard
  steps :second, :third

  def show
    @user = current_user
    render_wizard
  end

  def update
    @user = current_user
    @user.attributes = user_params
    render_wizard @user
  end

  private

  def finish_wizard_path
    users_path(current_user)
  end

  def current_user
    User.find_by(id: session[:user_id])
  end

  def user_params
    params.require(:user).permit(:email, :password, :zip, :address, :message)
  end
end
```

#### routes.rbの修正
以下を追加

```ruby
  resources :user_steps, only: [:index, :show, :update]
```

### 6. ウィザードのViewを作成
ウィザード入力に利用するViewを作成する。
作成するときにファイル名を `UserStepsController` で指定した `steps` の値と同じにする事。

- second.html.erb

```erb
<%= form_with model: @user, url: wizard_path do |form| %>
  <div class="field">
    <%= form.label :zip %>
    <%= form.text_field :zip, id: :user_zip %>
  </div>
  <div class="field">
    <%= form.label :address %>
    <%= form.text_field :address, id: :user_address %>
  </div>
  <div class="actions">
    <%= form.submit %>
  </div>
  <div>
    <%= link_to 'Skip', next_wizard_path %>
  </div>
<% end %>
```

- third.html.erb

```erb
<%= form_with model: @user, url: wizard_path do |form| %>
  <div class="field">
    <%= form.label :message %>
    <%= form.text_field :message, id: :user_message %>
  </div>
  <div class="actions">
    <%= form.submit %>
  </div>
  <div>
    <%= link_to 'Back', previous_wizard_path %>
  </div>
<% end %>
```
### 7. 結果
#### 元の入力画面
<img width="186" alt="スクリーンショット 2018-04-06 21.44.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/188887/09b4d8a2-bd6e-2ad3-df64-b1b9095b404e.png">
#### ウィザード形式になった画面
##### step1
<img width="190" alt="スクリーンショット 2018-04-07 2.27.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/188887/cc79d20d-24b2-d0c3-8ba2-7ef072c39b58.png">

##### step2
<img width="172" alt="スクリーンショット 2018-04-07 2.27.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/188887/ae166429-416a-c97b-97f4-8ccace600903.png">

##### step3
<img width="167" alt="スクリーンショット 2018-04-07 2.28.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/188887/5328b173-fb02-fa2a-3197-8f8c5fafa640.png">

## ちょっと中身を覗いてみる
### UserStepsController
#### include Wicked::Wizard
includeする事で wickedgemの機能が利用できるようになる。
また`UserStepsController`のアクションが実行される前に`#setup_wizard` が実行される。

```ruby
def setup_wizard
  check_steps!
  return if params[:id].nil?

  @step = setup_step_from(params[:id])
  set_previous_next(@step)
end
```
やっていることは主に以下の３つ

1. stepsのチェック
`include Wicked::Wizard`を行うと`steps`が必須となる。   
stepsを削除すると`UndefinedStepsError`エラーが発生する
2. 現在のstepの取得   
現在のステップは`params[:id]`で管理されています
3. 次のstepの保持

#### render_wizard

```ruby
def render_wizard(resource = nil, options = {}, params = {})
  process_resource!(resource)

  if @skip_to
    url_params = (@wicked_redirect_params || {}).merge(params)
    redirect_to wizard_path(@skip_to, url_params), options
  else
    render_step(wizard_value(step), options, params)
  end
end
```
第一引数のresource（例の場合だと`@user`）がある場合は`#save`を実行して次のstepへ`#redirect_to`が実行される。
resourceがない場合は現在のstepへ`#render`が実行される。

よって`#show`の場合はresourceがないため現在のstepを表示する。
`#update`の場合は入力内容を保存して次のstepへリダイレクトしている。

また次のstepへ行く際は必ず`#save`が実行されるので入力した値は保存されながら
次のstepへと遷移する。 ※１

#### stepはどのようにして保持しているか
次ステップへ移行する場合は最初の`include Wicked::Wizard`で書いてある通り
`params[:id]`で管理されている。
stepの内容を`#setup_wizard`で以下のインスタンス変数にセットしている。

1. `@step`   
現在のstepを保持
2. `@previous_step`  
前のstepを保持
3. `@next_step`  
次のstepを保持
4. `@skip_to`  
skip処理をするときに保持（`#render_wizard`でmodelを保存した時も使用）

## View
### next_wizard_path、previous_wizard_path
前述のインスタンス変数を利用してパスを生成している。

```ruby
def next_wizard_path(options = {})
  wizard_path(@next_step, options)
end

def previous_wizard_path(options = {})
  wizard_path(@previous_step, options)
end
```
## ある条件下においてはSTEPをスキップしたい場合
### UserStepsControllerの修正
`#show`に以下のように分岐を追加する。

```ruby
def show
  @user = current_user
  case step
  when :second
    jump_to :forth, word: 'secondとthirdをスキップしたよ！！！' if @user.zip.present?
  when :third
    skip_step word: 'thirdをスキップしたよ！！！' if @user.zip.blank?
  end
  render_wizard
end
```

stepで現在のstepが取得できるためそれを基準に分岐を実装する。
分岐以降での使用しているのは以下の２つ

1. #jump_to   
任意のstepに遷移できる、第１引数にstepsのいずれかの値を渡す。
2. #skip_step   
次のstepに遷移できる。

いずれも上記のメソッドを呼び出した後に`#render_wizard`を呼び出すことが必要。
上記の２つのメソッドを呼び出すと`@skip_to`に値を入れているため`#render_wizard`にて
指定のstepへ遷移するようになる。

修正したソースはこちらのbranch
[GitHub - mojihige/wicked-example at skip-step](https://github.com/mojihige/wicked-example/tree/skip-step)

## まだわかっていない点
※１について 
保存しないと次へ進めないと書いていますが`render_wizard`にパラメタを渡してそれを
遷移先で保持していけば保存しなくても遷移していけそうですが何か違う気がします。
（そもそもそのような使い方を想定していないと思う）

また調べることがあれば追記します。