railsを使った複数画面の登録機能の実装

# railsとjqueryと使った複数登録画面の実装

こんにちわ！この記事ではrailsとjqueryを使った複数画面の登録機能の実装について書いて行きたいと思います。
### プレビュー
![複数画面投稿.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348373/098ec3dd-bb51-6f0e-dbe6-7f2c55ab48cd.gif)
こんな感じの登録画面ですね！皆さんも一度は目にしたことがあるのではないでしょうか？
画面遷移自体はjqueryを使って隠れている分を出していってるだけです！
今回はここにそれぞれの画面でバリデーションが行えるようにしたいと思います！
![validation.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348373/c24cb09d-649f-b4b6-0dc9-66587a576263.gif)
具体的にはこんな感じですね！

###ステップ
ユーザが入力する
    ↓
次へボタンを押すとajaxでコントローラーにリクエストを送ってでvalidationを行う
    ↓
問題がなければ次の画面に遷移する、validationで引っかかれば画面遷移はせず、alertを出してあげる

流れになります！

###具体的な実装
各ページの実装のvalidationのための実装はほぼ同じなので今回は最初のページだけのコードを見せつつ説明をして行きたいと思います。

まずはview

```html:new.html
.registration-container
  .registration-header
    %h3
      registration
  .registration-view-part
    = form_for @user, html: {class: 'registration-form clearfix'} do |f|
      .first-step-registration
        .field
          .field-label
            = f.label '名前(名)'
          .field-input#first_name_input
            = f.text_field :first_name, id: 'first_name'
        .field
          .field-label
            = f.label '名前(性)'
          .field-input#family_name_input
            = f.text_field :family_name, id: 'family_name'
        .field
          .field-label
            = f.label 'ニックネーム'
          .field-input#nickname_input
            = f.text_field :nickname, id: 'nickname'
        .field
          .field-label
            = f.label '電話番号'
          .field-input#phone_number_input
            = f.text_field :phone_number, id: 'phone_number'
        .btn#first-step-btn
          next
      .second-step-registration
        .field
          .field-label
            = f.label '性別'
          .field-input
            = f.select :sex, { '男の子': :male, '女の子': :female }
        .field
          .field-label
            = f.label 'パスワード'
          .field-input#password_input
            = f.password_field :password, id: 'password'
        .field
          .field-label
            = f.label 'パスワード（確認）'
          .field-input#password_confirmation_input
            = f.password_field :password_confirmation, id: 'password_confirmation'
        .btn#second-step-btn
          next
      .third-step-registration
        .field
          .field-label
            = f.label '学年'
          .field-input#grade
            = f.select :grade, {'大学1年': :grade_1, '大学2年': :grade_2,'大学3年': :grade_3,'大学4年': :grade_4 }
        .field
          .field-label
            = f.label '大学'
          .field-input#university_input
            = f.text_field :university, id: 'university'
        .field
          .field-label
            = f.label 'Eメールアドレス'
          .field-input#email_input
            = f.email_field :email, id: 'email'
        = f.submit 'create account', class: 'registration-submit-btn'
```
これが登録画面のview部分の実装です。この全体のviewをcssで３つに分割しているように見せています。そして画面遷移はjqueryを使って隠れている部分を出して行きます。

```css:new.scss
.registration-container {
 width: 800px;
 height: 650px;
 padding: 30px 0;
 margin: 30px auto;
 background: #fff;
  .registration-header {
    width: 200px;
    margin: 0 auto;
    height: 30px;
    line-height: 30px;
    text-align: center;
    h3 {
      font-size: 30px;
    }
  }
  .registration-view-part {
    width: 480px;
    height: 550px;
    margin: 30px auto;
    overflow: hidden;
    border: 1px solid black;
    .registration-form {
      width: 2000px;
      height: 440px;
      .clearfix::after {
        content: "";
        display: block;
        clear: both;
      }
      .first-step-registration {
        border-box: box-sizing;
        width: 400px;
        height: 500px;
        padding: 40px;
        float: left;
        .field {
          width: 400px;
          height: 70px;
          margin: 0 30px 30px 30px;
          .field-label {
            margin-bottom: 10px;
          }
          .field-input {
            width: 80%;
            input {
              width: 90%;
              height: 30px;
              padding: 5px 10px;
              border-radius: 5px;
              border: 1px solid black;            
            }
            select {
              width: 100px;
              height: 40px;
            }
          }
          p {
            color: red;
          }
        }
        .btn {
          width: 100px;
          height: 40px;
          margin: 50px auto 30px auto;
          display: block;
          border-radius: 3px;
          font-size: 20px;
          border: 1px solid black;
          text-align: center;
          line-height: 40px;
          cursor: pointer;
        }
      }
      .second-step-registration,
      .third-step-registration {
        border-box: box-sizing;
        width: 400px;
        height: 400px;
        padding: 40px;
        float: left;
        .field {
          width: 400px;
          height: 100px;
          margin: 0 30px 30px 30px;
          .field-label {
            margin-bottom: 10px;
          }
          .field-input {
            width: 80%;
            input {
              width: 90%;
              height: 30px;
              padding: 5px 10px;
              border-radius: 5px;
              border: 1px solid black;            
            }
            select {
              width: 100px;
              height: 40px;
            }
          }
          p {
            color: red;
          }
        }
        .btn {
          width: 100px;
          height: 40px;
          margin: 0 auto;
          display: block;
          border-radius: 3px;
          font-size: 20px;
          border: 1px solid black;
          text-align: center;
          line-height: 40px;
          cursor: pointer;
        }
        .registration-submit-btn {
          margin: 0 auto;
          display: block;
          border-radius: 3px;
          font-size: 20px;
          border: 1px solid black;
          text-align: center;
          line-height: 40px;
          padding: 5px;
        }
      }
    }
  }
}
```
参考までにcssのファイルも載せておきます。

そして次のページに遷移するタイミングでvalidationを行いたいのですがここで重要になってくるのがreformというgemです。このgemはバリデーションを行うためのgemでファイルに属性を記述することでバリデーションしてくれます。

まずは次にボタンを押した段階で非同期通信でコントローラーにリクエストを送ります。

```js:registration.js
  $('#first-step-btn').on('click', function() {
    var first_name = $('#first_name').val();
    var family_name = $('#family_name').val();
    var nickname = $('#nickname').val();
    var phone_number = $('#phone_number').val();
    var btn = $(this)
      $.ajax({
        type: 'POST',
        url: '/first_step',
        data: { registrations: { first_name: first_name, family_name: family_name, nickname: nickname, phone_number: phone_number} },
        dataType: 'json'
      })
      .done(function(message) {
        if(message.length != 0) {
          $('#nickname_input > p').remove();
          $('#phone_number_input > p').remove();
          alert_first_step_error(message)
          console.log('bad')
        } else {
          $('.registration-form').css({
            'transform': 'translate(-480px)',
            'transition-duration': '1s'
          })
        }
      })
    }
  })
```

ここで最初のページで入力された名前等の情報をコントローラーの送ります。

```ruby:registrations_controller.rb
class RegistrationsController < ApplicationController

  def first_step
    @form = Registrations::FirstStepUsecase.new(first_step_params).execute
    if @form.errors.empty?
      render json: []
    else
      render 'first_step_errors', formats: 'json', handlders: 'jbuilder'
    end
  end

  private

  def first_step_params
    params.require(:registrations).permit(:first_name, :family_name, :nickname, :phone_number)
  end

end
```

受け取ったパラメーターをRegistrations::FirstStepUsecaseクラスに渡しています。

```usecases/registrations/first_step_usecase.rb
module Registrations
  class FirstStepUsecase
    attr_reader :param, :form
    def initialize(param)
      @param = param
      @form = Registrations::FirstStepForm.new(User.new)
    end

    def execute
      return form unless form.validate(param)
      form
    end
  end
end
```

そしてここでreformの出番です。

```forms/regisstrations/first_step_form.rb
require "reform/form/validation/unique_validator"
module Registrations
  class FirstStepForm < Reform::Form

    property :first_name
    property :family_name
    property :nickname
    property :phone_number

    validates :first_name, presence: true
    validates :family_name, presence: true
    validates :nickname, presence: true, unique: true
    validates :phone_number, presence: true, unique: true

  end
end

```

コードはこんな感じです。使い方によって書き方は違ってくるのですが、propertyのところはモデルの属性ですね。
validatesのところでどんなバリデーションをしたいのかを記述します。ここはモデルのファイルに記述するバリデーションと似ていますね。
詳しい使い方に関しては以下の記事やドキュメントを参考にしてみてください。
http://trailblazer.to/gems/reform/
https://qiita.com/subaru-shoji/items/e548f10a871938bedd06
英語のドキュメントは読むのが大変だと思うのですが、一番詳しく書いてあるので頑張って読んでみてください笑

```usecases/registrations/first_step_usecase.rb
module Registrations
  class FirstStepUsecase
    attr_reader :param, :form
    def initialize(param)
      @param = param
      @form = Registrations::FirstStepForm.new(User.new)
    end

    def execute
      return form unless form.validate(param)
      form
    end
  end
end
```



executeの部分でバリデーションを行なっています。エラーがあればエラー付きのformを返します。無ければ普通にformを返します。
そして再びコントローラに戻ります。

```ruby:registrations_controller.rb
  def first_step
    @form = Registrations::FirstStepUsecase.new(first_step_params).execute
    if @form.errors.empty?
      render json: []
    else
      render 'first_step_errors', formats: 'json', handlders: 'jbuilder'
    end
  end
```

ここではエラーが無ければ何も返さずにエラーがあればエラーの内容をjson形式で返すためにjbuilderに渡して
クライアントサイドに返します。

```first_step_errors.json.jbuilder
if @form.errors.messages[:nickname].present?
  json.nickname "このニックネームは使用されています。"
end

if @form.errors.messages[:phone_number].present?
  json.phone_number "この電話番号は使用されてます"
end
```

クライアント側に戻ってし処理を続けます。

```js:registration.js
      .done(function(message) {
        if(message.length != 0) {
          $('#nickname_input > p').remove();
          $('#phone_number_input > p').remove();
          alert_first_step_error(message)
          console.log('bad')
        } else {
          $('.registration-form').css({
            'transform': 'translate(-480px)',
            'transition-duration': '1s'
          })
        }
      })
```
先ほどのjsファイルの部分でエラーがあればエラー文を組み立てる、無ければ次のページに遷移するという実装の部分です。 alert_first_step_error(message)の部分がエラー文を組み立てる関数になっているのですがここでは省きます。

次のステップもやることは変わりません。

だいぶ大雑把に書きましたが、参考になれば嬉しいです。

## 参考

https://qiita.com/subaru-shoji/items/e548f10a871938bedd06
http://trailblazer.to/gems/reform/
https://qiita.com/akichim21/items/afe961ea752f894b389b