Rails6 + devise + モーダルウィンドウでリアルタイムバリデーション機能を実装してみる

## Rails6でsignup時にjquery-validation-engineを使ってリアルタイムにバリデーションをかけてチェックする

## はじめに
そもそもリアルタイムバリデーションってなんやねん！笑
多分こんな感じです↓
[![Image from Gyazo](https://i.gyazo.com/da56957304ff8cff8c004fac89a771d6.gif)](https://gyazo.com/da56957304ff8cff8c004fac89a771d6)

## 対象
- ライブラリやプラグインに抵抗がない方
- Rails6.0でdeviseを使ってモーダルウィンドウを表示してみたい方
- 簡単にリアルタイムでバリデーションをかけてみたい方
- 楽したい方(だいじ😁)

## 環境
Ruby v2.6.4
Rails v6.0.2.1
Haml v5.1.2

## 今回使用するもの
devise (gem)
Bootstrap4
jquery-validation-engine (プラグイン)

## Rails6.0のインストール(既にインストールされている方は飛ばしておｋ)
こちらの記事を参考にyarnとRails6.0をインストール↓
https://qiita.com/sasakura_870/items/44baadd3546eae4554c1

## yarnでBootstrap4をRailsに導入(既に導入されている方は飛ばしておｋ)

yarnコマンドでbootstrapとそれに依存しているjquery, popper.jsを追加

```
$ yarn add bootstrap jquery popper.js
```

jquery、popper.js をいつでも使用できるように**ProvidePlugin**を設定する

```config/webpack/environment.js
const {environment} = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.append('Provide', new webpack.ProvidePlugin({
  $: 'jQuery',
  JQuery: 'jquery',
  Popper: ['popper.js', 'default']
}))

module.exports = environment
```

Bootstrapをインポート

```app/javascript/packs/application.js
require("@rails/ujs").start()
require("@rails/activestorage").start()
require("channels")

~~ 省略 ~~

import 'bootstrap' # 追加
import "../stylesheets/application" # 追加

```

`app/javascript/stylesheets/application.scss`を作成し、その中にbootstrapのscssをインポート

```app/javascript/stylesheets/application.scss
@import "~bootstrap/scss/bootstrap";
```

application.html.haml(erb) の`stylesheet_link_tag`を`stylesheet_pack_tag`に変更する
※ 自分の環境はhamlを使っています。erbでも同様です
  色々訳あって、turbolinksは外しています✋

```app/views/layouts/application.html.haml
= stylesheet_pack_tag 'application'
```

これで**jQuery**と**Bootstrap**の準備が完了です！


## deviseの導入
**devise**はユーザー登録、ログイン機能を提供するgemです

以下手順

Gemfileにdeviseを記述

```:Gemfile
gem 'devise'
```

deviseをインストール
(もしサーバーが立ち上がっていたら一度切ってください)

```
$ bundle install
```

deviseのジェネレーターを生成

```
$ rails g devise:install
```

deviseのviewを生成

```
$ rails g devise:views
```

Userモデルをdeviseに紐づけて生成

```
$ rails g devise user
```

migrateする

```
$ rake db:migrate
```

## トップページ用の適当なビューファイルを作成

```
$ rails g controller tops index
```

ルーティングを変更

```routes.rb
Rails.application.routes.draw do
  devise_for :users
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root "tops#index"
end

```

## deviseのビューファイルをBootstrapを使ってスタイリング

signup時のビューを部分テンプレートにしたいので、`app/views/devise/registrations/new.html.haml`を`app/views/devise/registrations/_new.html.haml`にして、
以下のようにbootstrapを使ってモーダル表示させるようにする
（ここではidをsignUpModalとしてるが自分の好きな名前で良い）

```app/views/devise/registrations/new.html.haml
.modal.fade#signUpModal{tabindex: "-1", role: "dialog", "aria-hidden": true}
  .modal-dialog{role: "document"}
    .modal-content
      .modal-header.d-block
        %h5.modal-title.text-center ユーザー登録
      .modal-body
        = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
          = render "devise/shared/error_messages", resource: resource
          .field.form-group
            = f.label :nickname
            %br/
            = f.text_field :nickname, autofocus: true, autocomplete: "nickname", class: "form-control"
          .field.form-group
            = f.label :email
            %br/
            = f.email_field :email, autofocus: true, autocomplete: "email", class: "form-control"
          .field.form-group
            = f.label :password
            - if @minimum_password_length
              %em
                (#{@minimum_password_length} characters minimum)
            %br/
            = f.password_field :password, autocomplete: "new-password", class: "form-control"
          .field.form-group
            = f.label :password_confirmation
            %br/
            = f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control"
          .actions.form-group
            = f.submit "Sign up", class: "btn btn-primary form-control"

```

トップページを以下のようにマークアップ
リンクに`new.html.haml`で設定したidをdata属性で指定

```index.html.haml
.jumbotron
 %h1.display-4 Title
 %h2.read this is a bootstrap jumbotron style
 %hr.my-4
 = link_to "モーダル", new_user_registration_path, class: "btn btn-primary btn-lg", type: "button", "data-toggle": "modal", "data-target": "#signUpModal"
 = render "devise/registrations/new"
```

これでリンクボタンを押すとsignup画面が上からフワ〜ンと現れるようになる

## jquery-validation-engineの導入
リアルタイムにバリデーションをかけるための**jquery-validation-engine**というプラグインを以下のGithubからダウンロードします↓
https://github.com/posabsolute/jQuery-Validation-Engine

Installationという項目のところに *tar.gz 3.x.x or zip 3.x.x* とあるので 
**zipファイル**をダウンロードします
(linuxの方は**tar.gz**の方で)

展開したら中に入っている
`jquery.validationEngine.js` (プラグイン本体)
`jquery.validationEngine-ja.js` (プラグイン日本語化)
を
`app/javascript/packs`ディレクトリに置く

次に
`validationEngine.jquery.css`を`app/javascript/stylesheets`ディレクトリに置く

## application.jsの設定
Rails6.0ではjavascriptファイルとcssファイルのコンパイルが従来のアセットパイプラインから**webpacker**が標準になったため、ブラウザからjavascript、cssを読み込ませるための場所(**エントリーポイント**)が、`app/javascript/packs/application.js`になっています。

よって、ここを基点としてjs、cssファイルなどを読み込んでいきます。
先程の`jquery.validationEngine.js`と`jquery.validationEngine-ja.js`を`app/javascript/packs/application.js`に以下のようにして読み込ませます

```app/javascript/packs/application.js
require("@rails/ujs").start()
require("@rails/activestorage").start()
require("channels")
require("./jquery.validationEngine")  # 追加
require("./jquery.validationEngine-ja")  # 追加

import 'bootstrap'
import "../stylesheets/application"
```

## stylesheets.scssの設定

`validationEngine.jquery.css`を`validationEngine.jquery.scss`に拡張子リネームし、以下にインポート

```app/javascript/stylesheets/application.scss
@import "~bootstrap/scss/bootstrap";
@import "./validationEngine.jquery.scss";  # 追加
```

## modal.jsを作成する

`app/javascript/packs/modal.js`を作成し、以下のように記述

```app/javascript/packs/modal.js
$(function(){

  $(".new_user").validationEngine({
    promptPosition: "inline"
  });

  
});
```

**promptPosition**でエラーメッセージの表示位置をinputの下に表示するようにしている

`app/javascript/packs/application.js`に`modal.js`を読み込ませる

```app/javascript/packs/application.js
~~ 省略 ~~
require("./jquery.validationEngine-ja")
require("./modal")
```

これでバリデーションの準備が整いました！（長かった）

## サインアップ画面にバリデーションを付与

以下がバリデーションを付与したソースコード

```app/views/devise/registrations/_new.html.haml
~~ 省略 ~~
      .modal-body
        = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
          = render "devise/shared/error_messages", resource: resource
          .field.form-group
            = f.label :nickname
            %br/
            = f.text_field :nickname, autofocus: true, autocomplete: "nickname", class: "form-control validate[required]"
          .field.form-group
            = f.label :email
            %br/
            = f.email_field :email, autofocus: true, autocomplete: "email", class: "form-control validate[required, custom[email]]"
          .field.form-group
            = f.label :password
            - if @minimum_password_length
              %em
                (#{@minimum_password_length} characters minimum)
            %br/
            = f.password_field :password, autocomplete: "new-password", class: "form-control validate[required, minSize[6]]", id: "passwd"
          .field.form-group
            = f.label :password_confirmation
            %br/
            = f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control validate[required, equals[passwd]]"
          .actions.form-group
            = f.submit "Sign up", class: "btn btn-primary form-control"
```

***validate[required]*** --- 入力必須

```
= f.text_field :nickname, class: "form-control validate[required]"
```

***custom[email]*** --- メールアドレスかチェック

```
= f.email_field :email, class: "form-control validate[required, custom[email]]"
```

***minSize[10]*** --- 10文字以上であるかチェック

```
= f.password_field :password, class: "form-control validate[required, minSize[6]]"
```

***equals[passwd]*** --- 同じ値であるかチェック

```
= f.password_field :password, class: "form-control validate[required, minSize[6]]", id: "passwd" # idを付与
```

idと同じかチェックする

```
= f.password_field :password_confirmation, class: "form-control validate[required, equals[passwd]]"
```

上記の条件で最低限のバリデーションはできると思います！


## まとめ

久々にQiita書きました。ポートフォリオの改変をしていたときに、
signup画面をモーダルウィンドウ化してバリデーションをかけてみたいと思い記事を書いてみました。
少し長々となってしまいましたが、バリデーションっていってもjqueryのプラグイン入れるぐらいで特別なことは得にしていない感じです。
少し面倒なところといえば、Bootstrapの導入ぐらいでかね?
jqueryバリデーションは他にも色々な条件や機能があるみたいですので、時間があったら研究してみたいですね^^

## 参考記事

- [jquery-validation-pluginの使い方]
(http://tech-log.hatenablog.jp/entry/2015/12/26/044401#%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9)

- [bootstrap4 - Modal](https://getbootstrap.com/docs/4.4/components/modal/)