railsで一意制約とかのリアルタイムバリデーション

#初めに

RailsとjQueryでインクリメンタルサーチ
https://qiita.com/hiroki_y/items/27d28a89b1c05ec75662

こちらを参考にしてリアルタイムで一意制約などのバリデーションを実装してみます。

例えばすでに登録されているユーザー名なのかをリアルタイムで表示したい場合とかです。




#準備

とりあえずsimple_formを使うのとそれに合わせてbootstrapも使うのでそれをやる

以下を参考にしてください。

https://qiita.com/sibakenY/items/951244811404b8e4d38b

そして

```
rails g model user name:string email:string password:string
rails g controller users 
```

routes.rbに

```ruby 
Rails.application.routes.draw do
  resources :users, :only => [:index,:new,:create]
  get 'users/name'
  get 'users/email'
end
```

とする。

resourcesでshowが使える状況だとこのままだとusers/name,users/emailでshowアクションが行われてしまうのでonly,exceptでshowを使えなくするかしてください。


#実装

users_controller viewに関してまず登録できるようにする

controller

```ruby:users_controller.rb

class UsersController < ApplicationController

    def index 
    end 

    def new 
        @user = User.new 
    end 

    def create 
        @user = User.create(user_params)
        if @user.save 
            redirect_to users_path 
        else 
            render :new     
        end 
    end 

    private 

    def user_params 
        params.require(:user).permit(:name,:email,:password)
    end     
end
```


view

```erb:new.html.erb
<br>
<br>
<br>
<br>
<div class="container">
    <%= simple_form_for(@user) do |f| %>
        <div class="form-input">
            <%= f.input :name, id: "nameId" %>
        </div>
        <div class="form-input">
            <%= f.input :email %>
        </div>
        <div class="form-input">
            <%= f.input :password %>
        </div>

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

```


これで登録ができます。


次にinputのnameに文字を入力するたびにその名前が有効なのか表示できるようにしたいと思います。

users_controllerに

```ruby:users_controller.rb
    def name 
        raise unless params[:q]

        result = nil
        if User.find_by(name: params[:q])
            result = "findUser" 
        else 
            result = "notFind"
        end 

        render :json => { :result => result }
        return true 
    end 
```

このメソッドを追加します。

これはparams[:q]があった場合User.find_by(name: params[:q])でユーザを探し

存在した場合はresultにfindUserを存在しなかったらnotFindを入れてそれをrender :jsonでjson形式で渡しています。


次にjavascriptを書いていきます。

javascriptを書く場所はassets/javascriptの中だと思うのですが自分はあまりここに書くのが好きではないのでviewsのnew.html.erbに直接書いていきます。

それで書くのがこれ

```javascript:new.html.erb 
<script>
function nameSearch() {
    clearTimeout(nameSearch.timeid);

    nameSearch.timeid = setTimeout(function() {
        
        var query = tgtInput.value;
        if (query.length == 0) return false;

        jQuery.ajax({
            type : 'GET',
            url : '/users/name',
            data : {
                q : query 
            },
            timeout : 1500,
            success:function(data){
                if (data.result == "notFind") {
                    console.log("you can create with this user name")
                    tgtInput.classList.remove('is-invalid')
                    tgtInput.classList.add('is-valid')  
                    validMessage.classList.add('text-success')  
                    validMessage.innerHTML = "有効なユーザー名です"
                } else {
                    console.log("you cannot create with this user name")
                    tgtInput.classList.remove('is-valid')
                    tgtInput.classList.add('is-invalid')
                    validMessage.classList.add('text-danger')
                    validMessage.innerHTML = '既に使われているユーザー名です'
                }
            }
        });

    
        
    },500);
};

nameSearch.timeid = "";

var tgtInput = document.getElementById('user_name')
var validMessage = document.getElementById('nameValidOrInvalid')
tgtInput.onkeydown = nameSearch;
</script>
```

また文字を出力するために必要になるdivを追加する

```erb:new.html.erb
<div class="container">
    <%= simple_form_for(@user) do |f| %>
        <div class="form-input">
            <%= f.input :name, id: "nameId" %>
        </div>
        <p id="nameValidOrInvalid"></p> <= 追加
        <div class="form-input">
            <%= f.input :email %>
        </div>
        <div class="form-input">
            <%= f.input :password %>
        </div>

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

```


まず最初にnameSearchというfunctionを設定しています。

このnameSearchの中のjQuery.ajaxで非同期通信を行っています。

data : {}でパラメータを渡していて返ってくるデータはdata.resultで取得できます。

formのidに関してはsimple_formを使うと勝手に設定してくれていて今回の場合はnameの部分のidはuser_nameになっています。

simple_formの場合インプットのクラス名にis-validがあると緑色にis-invalidがあると赤色になるのでこれを利用して

classList.remove,classList.addを使いクラス名を変更しています。

そうするとこんな感じになると思います


![form.png](https://qiita-image-store.s3.amazonaws.com/0/207921/420d6cf4-4e0f-c83a-3ad5-6d571f1264ca.png)



#ついでにEmailでも同じことをしてみる

Emailでもバリデーションを実装してみる

emailの場合まずemailのフォーマットなのか（xxx@xxx.xxみたいな感じ）ということと既に登録されているのかを検証する

まずusers_controller

```ruby:users_controller

    def email 
        raise unless params[:q]

        reuslt = nil 
        if User.find_by(email: params[:q])
            result = "findEmail"
        else 
            result = "notFind"
        end 

        render :json => { :result => result }
        return true
    end 
```

これを追加する

そしてview側を

```erb:new.html.erb
<div class="container">
    <%= simple_form_for(@user) do |f| %>
        <div class="form-input">
            <%= f.input :name, id: "nameId" %>
        </div>
        <p id="nameValidOrInvalid"></p>
        <div class="form-input">
            <%= f.input :email %>
        </div>
        <p id="emailValidOrInvalid"></p> <= これを追加
        <div class="form-input">
            <%= f.input :password %>
        </div>

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

javascript部分は

```javascript:new.html.erb
function emailSearch() {
    clearTimeout(emailSearch.timeid);

    emailSearch.timeid = setTimeout(function() {
        
        var query = tgtInputEmail.value;
        if (query.length == 0) return false;

        jQuery.ajax({
            type : 'GET',
            url : '/users/email',
            data : {
                q : query 
            },
            timeout : 1500,
            success:function(data){
                if (data.result == "notFind") {
                    console.log("you can create with this user name")
                    if (emailRegexp.test(tgtInputEmail.value)) {
                        tgtInputEmail.classList.remove('is-invalid')
                        tgtInputEmail.classList.add('is-valid')

                        validMessage.classList.remove('text-danger')  
                        validMessage.classList.add('text-success')  
                        validMessage.innerHTML = "有効なメールアドレスです"
                        console.log("changed success")
                    } else {
                        tgtInputEmail.classList.remove('is-valid')
                        tgtInputEmail.classList.add('is-invalid')

                        validMessage.classList.remove('text-success')
                        validMessage.classList.add('text-danger')
                        validMessage.innerHTML = '無効なメールアドレスです'
                        console.log("changed danger")
                    }
                    
                } else {
                    console.log("you cannot create with this user name")
                    tgtInputEmail.classList.remove('is-valid')
                    tgtInputEmail.classList.add('is-invalid')

                    validMessage.classList.remove('text-success')
                    validMessage.classList.add('text-danger')
                    validMessage.innerHTML = '既に使われているメールアドレスです'
                }
            }
        });   
    },500);
};


var tgtInputEmail = document.getElementById('user_email')
var validMessageEmail = document.getElementById('emailValidOrInvalid')
var emailRegexp = /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/;
tgtInputEmail.onkeydown = emailSearch;
```

ほとんどさっきと同じ。

違いはdata.result == "notFind"の場合にまたifでemailのフォーマットなのかの条件分岐をしています。


おわり:sunny: