RailsとjQueryで非同期通信の実装

RailsとjQueryでajaxを用いた非同期通信の実装。
スクールで学んでいて結構苦戦した分野でもあるので忘れないようにメモしておきます。
今回は、セレクトボックスの値が変更されたら、それに該当するレコードを表示し直すというものにしてみます。
失敗例なども載せているため、長めの記事となっております。

## 環境
Rails 5.2.3
jQuery

## ajax(非同期通信)とは
リクエスト後にレスポンスが帰ってきた際、ブラウザが再読み込みされること無く通信が行われる通信方法。
"Asynchronous JavaScript + XML"と表現され、略してAjax（エイジャックス）と呼ばれる。
要は、Webページの内容を変更するためには画面遷移やリロードする必要があるが、**ajaxを使用すればリロードせずにページの内容を変更することが可能。**

## 処理の流れ
### 1. 非同期通信をするためのルーティングとコントローラー
**ルーティング**

```routes.rb
resources :records do
  collection do
    get 'history'
    get 'select_year', defaults: { format: 'json' }
  end
end
```
historyはレコードが表示されるページで、select_yearがajax通信のリクエスト先になっています。
htmlで使う予定もないので最初からdefaults: { format: 'json' }でjson形式に限定しています。

**コントローラー**

```records_controller.rb
def history
  # donation_dayカラムのみを抽出
  history_year = Record.pluck(:donation_day)

  year_array = []
  history_year.each do |history|
    history_day = Date.strptime(history,'%Y年%m月%d日')
    history_year = history_day.year
    year_array << history_year
  end
  # 重複を削除
  @year_array = year_array.uniq

  last_year = @year_array[0]

  # 最初に表示するレコードをdonation_dayであいまい検索する
  @first_view = Record.where(user_id: current_user.id).where("donation_day LIKE ?", "%#{last_year}%")
end

def select_year

end
```
ここでは、ビューで使う&#064;year_arrayと&#064;first_viewという変数を定義しています。
select_yearの中身は後で記述します。

### 2. ビューの作成

```history.html.haml
.record_main
  .record_main__box.row
    .record_main__box__text.mx-auto
      %h2 今までの履歴
  献血年
  = select_tag 'history_year', options_for_select(@year_array), class: "history_year"

  .history_header.row
    .history_header__box.col-12
      - 2.times do |number|
        .history_header__category.col-4.float-left.text-center
          - case number
          - when 0
            %h4 献血日
          - when 1
            %h4 献血方法
  .history_result
    - @first_view.each do |rec|
      .history_box.row
        .history_list.col-12.d-flex
          - 2.times do |number|
            .history_list__category.col-md-4.text-center{class: "category#{number}"}
              - case number
              - when 0
                = rec.donation_day
              - when 1
                = rec.inspection_method_i18n
```
select_tagでセレクトボックスを作って、中身は先ほどのコントローラーで定義した@year_arrayを使っています。

### 3. jQueryでajaxの記述

```history.js
$(document).on('turbolinks:load', function() {
  $(function() {

    // セレクトボックスの値が変更されたら発火
    $('#history_year').change(function() {
      var year = $(this).val(); // セレクトボックスの値を取得
      var result_area = $('.history_result');

      // ajaxによる非同期通信
      $.ajax({
        type: 'GET',
        url: '/records/select_year',
        data: { keyword: year },
        dataType: 'json'
      })
    });
  });
});
```
changeメソッドを使って、セレクトボックスの値が変更されたらイベントが発火するように設定。
ajaxではリクエスト先として最初に設定したrecords_controllerのselect_yearアクションを指定し、渡すデータとして、yearの値をkeywordという名前の変数に入れています。

### 4. コントローラーで該当データを検索
```records_controller.rb
def select_year
  @select_year = Record.where(user_id: current_user.id).where("donation_day LIKE ?", "%#{params[:keyword]}%")
end
```
検索したいカラムにLIKE句であいまい検索をかけて、そのためのparams[:keyword]は先ほどのajaxで送られてきたdataになります。

### 5. jbuilderを使用して、レコード情報をJSON形式で返す
app/views/records/select_year.json.jbuilder

```select_year.json.jbuilder
json.array! @select_year do |record|
  json.day record.donation_day
  json.method record.inspection_method_i18n
end
```
ajax関数で、datatypeをjsonとしているのでレスポンスはjson型で返ってきます。
そのため、jbuilderでjson型のデータを作成します。
jbuilderのファイルの置き場所はviewディレクトリのviewに関わるコントローラーのディレクトリ内で、名前はリクエストしたアクション名と同じになります。
コントローラーで検索したデータは配列になっているので１行目はこの書き方になります。
今回必要なデータは「日付」と「方法」の２つとなります。

### 6. 受け取ったJSONをdoneメソッドで受取り、HTMLを作成する
ここでつまづいた点があったのでそれも載せておきます。

#### 6-1 失敗例
```history.js
$(document).on('turbolinks:load', function() {
  $(function() {

    function appendList(record, result_area) {
      var result =  `<div class='history_box row'>
                      <div class='history_list col-12 d-flex'>
                        <% 2.times do |number| %>
                          <div class="category<%= number %> history_list__category col-md-4 text-center">
                            <% case number %>
                            <% when 0 %>
                              <%= ${record.day} %>
                            <% when 1 %>
                              <%= ${record.method} %>
                            <% end %>
                          </div>
                        <% end %>
                        <div class='col-3'>記録閲覧</div>
                      </div>
                    </div>`;
      result_area.append(result);
    }

    // セレクトボックスの値が変更されたら発火
    $('.history_year').change(function() {
      var year = $(this).val(); // セレクトボックスの値を取得
      var result_area = $('.history_result');

      // ajaxによる非同期通信
      $.ajax({
        type: 'GET',
        url: '/records/select_year',
        data: { keyword: year },
        dataType: 'json'
      })

      .done(function(records) {
        result_area.empty(); // 現在表示されているリストを削除
        records.forEach(function(record) { // jbuilderからのデータをforeachで分解
          appendList(record, result_area); // 関数呼び出し
        });
      })
      .fail(function() {
        alert('リストの表示に失敗しました')
      })
    });
  });
});
```
やりたかったのは、hamlで書いていたものをそのままhtmlの形式に直して突っ込んでしまおうということだったのですが・・・
**実行結果(大失敗)**
![demo](https://gyazo.com/a6a8c8e6fae0fa33eb41fee309ff61a3/raw)
このように、<% %>や<%= %>といったRubyのコードを埋め込む書き方は反映されずにそのまま表示されてしまいました。
どうやら、JavaScriptはブラウザ上で、railsはサーバ上で動くためJavaScriptの中にrailsの記法を入れても動かないらしいです。

#### 6-2 一応動く方法
```history.js
$(document).on('turbolinks:load', function() {
  $(function() {

    function appendList(record, result_area) {
      var result =  `<div class='history_box row'>
                      <div class='history_list col-12 d-flex'>
                        <div class="category0 history_list__category col-md-4 text-center">
                          ${record.day}
                        </div>
                        <div class="category1 history_list__category col-md-4 text-center">
                          ${record.method}
                          </div>
                        <div class='col-3'>記録閲覧</div>
                      </div>
                    </div>`;
      result_area.append(result);
    }

    // セレクトボックスの値が変更されたら発火
    $('.history_year').change(function() {
      var year = $(this).val(); // セレクトボックスの値を取得
      var result_area = $('.history_result');

      // ajaxによる非同期通信
      $.ajax({
        type: 'GET',
        url: '/records/select_year',
        data: { keyword: year },
        dataType: 'json'
      })

      .done(function(records) {
        result_area.empty(); // 現在表示されているリストを削除
        records.forEach(function(record) { // jbuilderからのデータをforeachで分解
          appendList(record, result_area); // 関数呼び出し
        });
      })
      .fail(function() {
        alert('リストの表示に失敗しました')
      })
    });
  });
});
```
値を表示する箱が２つだけなのでそのまま記述しました。
ただ、元のhamlでは繰り返し処理で箱を作っていたので、できればこちらでも同じように繰り返し処理で書きたいと思って以下のように修正しました。(その方が、表示する項目を増やしたいときにも修正しやすいのではと思いました)

#### 6-3 最終形
```history.js
$(document).on('turbolinks:load', function() {
  $(function() {

    function createList(record) {
      var lists = '';
      var category = '';
      // for文で繰り返し(繰り返し番号によって条件分岐)
      for ( var num = 0;  num < 2;  num++ ) {
        // 繰り返し番号での条件分岐
        if (num == 0) {
          category = record.day;
        } else {
          category = record.method;
        }

        var list = `<div class="history_list__category col-4 text-center px-0 category${num}">
                      ${category}
                    </div>`;
        lists += list;
      }
      return lists;
    }

    function appendList(lists, result_area) {
      var result =  `<div class='history_box row'>
                      <div class='history_list col-12 d-flex'>
                        ${lists}
                        <div class='col-3'>記録閲覧</div>
                      </div>
                    </div>`;
      result_area.append(result);
    }

    // セレクトボックスの値が変更されたら発火
    $('.history_year').change(function() {
      var year = $(this).val(); // セレクトボックスの値を取得
      var result_area = $('.history_result');

      // ajaxによる非同期通信
      $.ajax({
        type: 'GET',
        url: '/records/select_year',
        data: { keyword: year },
        dataType: 'json'
      })

      .done(function(records) {
        result_area.empty(); // 現在表示されているリストを削除
        records.forEach(function(record) { // jbuilderからのデータをforeachで分解
          var lists = createList(record) // リストを先に作成
          appendList(lists, result_area); // リストを元にHTML作成
        });
      })
      .fail(function() {
        alert('リストの表示に失敗しました')
      })
    });
  });
});
```
関数を２つ用意しました。

- appendList
  - 最終的なHTMLを生成するための関数
- createList
  - appendListで使用するHTMLをjbuilderから送られてきたデータを元に生成する関数
  - 元のhamlでのtimesのような繰り返し処理をfor文とif文で実装

**ちなみに**
説明し忘れていましたが、doneメソッド内で急に出てきたrecordsという引数にjbuilderから送られてきたデータが入っています。
配列の形になっているので、forEachを使って個々のデータにバラしています。

## 実行結果
![demo](https://gyazo.com/d86a69b55f267c2fbd142c62b1780c13/raw)

これで非同期通信の実装完了です。