Rails + jQueryでインクリメンタルサーチ(基礎)

最近アウトプット出来ていなく久々にjQueryに触れたので議事録がてら書いていきたいと思います。
以前もAjaxに関しての記事を書いたので少し内容が被ってしまいますが**今回はインクリメンタルサーチ**についてです。

##インクリメンタルサーチとは
> インクリメンタルサーチ（英語: incremental search）は、アプリケーションにおける検索方法のひとつ。 検索したい単語をすべて入力した上で検索するのではなく、入力のたびごとに即座に候補を表示させる。 逐語検索、逐次検索とも。
>> 
[wikipedia-インクリメンタルサーチ](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AA%E3%83%A1%E3%83%B3%E3%82%BF%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%81)

↓のような感じで検索ボタンを押さなくてもページ遷移なしで結果を表示してくれます。

[![Image from Gyazo](https://i.gyazo.com/f9bf6a26c199ce5fb8500dfb93b626cb.gif)](https://gyazo.com/f9bf6a26c199ce5fb8500dfb93b626cb)

##この記事の完成イメージ
見た目は質素なので機能だけを紹介していきます。

[![Image from Gyazo](https://i.gyazo.com/674fd509f948913c2f865d0775424300.gif)](https://gyazo.com/674fd509f948913c2f865d0775424300)


##コード
今回実装したコード

```haml:search.html.haml
.search-container
  = form_tag('/products/search', method: :get, placeholder:'') do
    = text_field_tag :name, :'', id:'searching-form', placeholder:'検索', autocomplete:'off'
  .result
    %ul
```

```javascript:search.js
$(document).on('turbolinks:load', function(){
  const inputForm = $('#searching-form');
  const url = location.href;
  const searchResult = $('.result ul');

  function builtHTML(data){
    let html = `
    <li>${data.name}</li>
    `
    searchResult.append(html);
  }

  function NoResult(message){
    let html = `
    <li>${message}</li>
    `
    searchResult.append(html);
  }

  // フォームに入力すると発火する
  inputForm.on('keyup', function(){
    const target = $(this).val();
    search(target);
  });

  // ajax処理
  function search(target){
    $.ajax({
      type: 'GET',
      url: url,
      data: {keyword: target},
      dataType: 'json'
    })
    .done(function(data){
      searchResult.empty(); //再度検索した際に前のデータを消す処理
      if (data.length !== 0) {
        data.forEach(function(data) { //dataは配列型に格納されているのでEach文で回す
          builtHTML(data)
        });
      } else {
        NoResult('該当する商品はありません')
      }
    })
    .fail(function(data){
      alert('非同期通信に失敗しました');
    })
  }
});
```

```js:search.json.jbuilder
json.array! @products do |product| 
  json.name product.name
end
```
```ruby:products_controller.rb
  def search
    @products = Product.where('name LIKE(?)', "%#{params[:keyword]}%")
    respond_to do |format|
      format.html #htmlを読み込んであげないとエラーが出るのでしっかりと記述
      format.json 
    end
  end
```

##処理の流れ

###1.(search.js)フォームに文字が入力されるとイベントが発生するようにkeyupメソッドを仕掛ける。
```js:search.js
  // const inputForm = $('#searching-form'); input要素
  inputForm.on('keyup', function(e){
    e.preventDefault();
    let target = $(this).val(); 
    search(target);　　//ajax通信はsearch()という関数にしています。
  });
```
val()はjQueryメソッドなのでjQuryオブジェクトに対して使用するように注意が必要です。
この時点で変数targetに値が入っているか確認するためにmacの方なら「F11 + fn」でコンソールが開けるので`console.log(target)`として確認しておくと確実です。



　↓
###2.(search.js)Ajaxでサーバーにリクエストを送信
まずはじめにAjaxリクエストを送信するオプションをキーと値のペアで指定します。
`$.ajax({//サーバーに送信するリクエストの設定})`
今回必要になる設定のオプションは↓

| キー | 値 |
|:--|:--|
| type  | リクエストのタイプ("GET"または"POST")を指定します。 |
| url |  Ajaxリクエストを送信するURLを指定します。(rake routesコマンドでリクエストを送信したいURIを確認しましょう。今回であれば'products/search')|
| data | サーバへ送信するデータです。キー/値のペアで設定します。|
| dataType | サーバから返ってくるデータの型を指定します(json, htmlなど)。 |


以上を踏まえた上で今回のコード

```js
    $.ajax({
      type: 'GET',
      url: 'products/search',
      data: {keyword: target},
      dataType: 'json'
    })
```
今回送信したいリクエストはユーザーやクレジットカードなどの情報をサーバーにPOSTするのではなく**サーバーにあるproductに関する情報をGET**したいので`type: 'GET'`となります。

**url**に関してはrake routesでリクエストを送りたいURIを確認出来ます。

`data: {keyword: target}`に関して
**keyword**はキーになるのでリクエストを送信した時にController側で**params[:keyword]として**受け取れます。
値である**target**は先ほどイベントが発生した時に定義したtargetです。

Railsのjbuilderという機能を使用するので**dataType**はjsonです！

**データの流れが分かりづらいですが、これでサーバー側にリクエストが送られました。次はRailsのController側での処理になります**

※Ajaxでリクエスト送信→Controllerでjson、もしくはhtml形式での処理を場合分け→jsonであればjbuilderへ処理を流す→jbuilderで処理されたデータを再びJavascriptで受け取りHTMLの追加や削除を行う。


###3.(products_controller.rb)コントローラーで値を受け取る
Ajaxで送信された値はコントローラーで受け取る事が出来ます。
`$.ajaxではtype, url, data, dataType`を指定しています。
**type: 'GET', url: 'products/search'はでした！**
これはproducts/searchに対してGETリクエストを送るという事です。
なのでRailsアプリケーションが`GET products/search`というHTTPリクエストを受け取った時にprodductsコントローラーのsearchアクションへ飛ぶように`routes.rb`で設定してあげましょう。

```ruby:routes.rb
  resources :products do
    collection do
      get 'search'
    end
  end

  # 下記のようにしても出来ますが自分は上のようにしています。
  get 'products/search' => 'products#search'
```
これでコントローラーで受け取る準備が出来ました。(確認されたい方はrake routesコマンドでルーティングを確認出来ます！)
次にコントローラーでの処理を見ます。

```ruby:products_controller.rb
  def search
    # @productsは次に紹介するjbuilderで必要になるインスタンス変数です
    @products = Product.where('name LIKE(?)', "%#{params[:keyword]}%")
    respond_to do |format|
      format.html 
      format.json 
    end
  end
```

`$.ajaxで設定したdata`がパラメーターとして送られてきます。
画像の3行目にあるのがパラメーターで記述した通り`keyword: value`の形になっています。
(今回は分かりやすく**keyword**にしているだけなのでkeyの名前は任意です。)
[![Image from Gyazo](https://i.gyazo.com/13417743245f91a4a67a4d1676c3f4cc.png)](https://gyazo.com/13417743245f91a4a67a4d1676c3f4cc)
Railsではparamsメソッドを使えばパラメーターの値が取得できるので、`params[:keyword]`としています。
インスタンス変数@productsにはwhereとLIKE句を使いDBから検索した該当するデータを**配列**として持っています。

フォーマット毎に処理を分けるには、respond_toを使用します。
今回は@productsに入っているデータが配列なのでjbuilderで配列を処理してJavascriptに返します。
`※respond_to というメソッドを使うと、リクエストがHTMLを求めているのか、もしくはJSONを求めているのかを拡張子やヘッダー情報を利用して、自動的に条件分岐してくれます。`



###4.(search.json.jbuilder)json形式で出力する
**jbuilder**はrails newコマンドでアプリケーションを作成した際にgemfileにデフォルトで記述されているgemで、入力データをJSON形式で出力するテンプレートエンジンです。

```js:search.json.jbuilder
json.array! @products do |product| 
  json.name product.name
end
```
@productsは配列でなので.array!を使用します。
jbuilderファイルでは基本的にjson.KEY VALUEという形で書くことができます。
こうすることによってJavaScriptファイル(search.js)に返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使うことができます。
今回はjson.nameがkey、product.nameがvalueになります。

[jbuilderについてはこちらの記事が詳しいです](https://qiita.com/ryouzi/items/06cb0d4aa7b6527b3645)

###5.(search.js)jbuilderからの出力を受け取り表示する

```js:search.js
  // データがあった場合に呼ばれる関数
  function buildHTML(dataFromSearchFunction){
    let html = `
    <li>${dataFromSearchFunction.name}</li>
    `
    searchResult.append(html);
  }

  // 該当するデータがなかった時に呼ばれる関数
  function NoResult(message){
    let html = `
    <li>${message}</li>
    `
    searchResult.append(html);
  }

  function search(target){
    $.ajax({
      type: 'GET',
      url: url,
      data: {key: target},
      dataType: 'json'
    })
    .done(function(data){
      // 通信が成功した時の処理
      searchResult.empty(); //再度検索した際に前のデータを消す処理
      if (data.length !== 0) {
        data.forEach(function(data) { //dataは配列型に格納されているのでEach文で処理
          buildHTML(data)
        });
      } else {
        NoResult('該当する商品はありません')
      }
    })
    .fail(function(data){
　　　// 通信が失敗した時の処理
      alert('非同期通信に失敗しました');
    })
  }
```

`.done(function(data){//処理})`で受け取っているdataは**jbuilderから送られてくるデータ**なのでeach文で一つ一つの値を定義している関数buildに渡している。
関数buildに入っている値はjbuilderで定義したキーとバリューの形で呼び出して使うことができるので`${data.name}`といった記述が可能になります！

以上になります。


jQueyでのajax処理は直感的ですごく扱いやすいと改めて感じました。
インフラに関するアウトプットが出来ていないので何か記事をあげたいなと思います。
ご覧いただきありがとうございました。
誰かの参考になれば幸いです。