Rails 5.1+jQueryでajaxを試す (罠にハマる)

# Rails5.1でajaxができない？

ajaxのやり方を聞かれたのでRails4時代の知識をドヤ顔で答えていたら､すっかりやり方がかわっていて浦島太郎感が半端ないです｡
(react? vue.js？ なにそれ)

そもそも『rails5.1 + jQuery(rails-ujs)でajaxをやる』という路線自体が､急速に消滅に向かっている空気を感じるので､最新の情報が全く見つからなかったです｡トホホ｡

## 結論

#### 1. jquery-ujsの代わりに､rails-ujsを使おう

[5.1からjquery依存が廃止](https://railsguides.jp/5_1_release_notes.html#%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%A7%E3%81%AEjquery%E4%BE%9D%E5%AD%98%E3%82%92%E5%BB%83%E6%AD%A2)されたのに伴い､ujs関連はrails-ujsとして切り出されたようです｡

```ruby:Gemfile
gem 'rails-ujs'

```
```js:application.js
//= require jquery
//= require rails-ujs
//= require turbolinks
//= require_tree .
```
turbolinksはお好みで｡

#### 2. イベント取得の方法が変わっているので要注意

上記と関連しているのですが､

```
  $('#hoge').on('ajax:success', function(event, data, status, xhr) { ... 
```

でよかったのが､

```
  $('#hoge').on('ajax:success', function(event) { ...
```

と変更になりました｡データの取り出しは､ こんな感じ｡

```
data   = event.detail[0];
status = event.detail[0];
xhr    = event.detail[0];
```

(`ajax:success` `ajax:error` ともに共通のかたちです）


## とりあえずやってみる

いくつか実装の方向はあるようですが･･

### 直接jsを返す

コントローラーで直接`hoge.js.erb` をレンダリングして返すヤツですね｡

そういや標準機能ですが､いろいろあってあんまり見ないですね｡

### JSONを返す

よく見るやり方としては､2つあるような気がしています｡

##### 1) ajaxリクエスト送信､レスポンス受信ともにjsで書く ####

ボタンクリックを待ち受けておいて､
```
$.ajax({...}).done(...).fail(...)
```

みたいにするアレですね｡

##### 2) リクエスト送信はフォームにまかせ､JSONを受け取る部分をjsで書く **(今回はコレ)** ####

ちょっと楽をしたいので､送信は `link_to ... remote: true`でrailsまかせ｡
受信部分だけをjsで書いてみる

**⇒ 罠だった｡**

## やってみる

*ブラウザでボタンを押したら､サーバーがランダムに選んだ｢じゃんけん｣の手を返してくる*

･･･というだけの､手抜きサンプルをつくってみようと思います｡
教える都合上､js､HTMLともにクラシックな記法で書いていきます｡

```ruby:config/routes.rb
Rails.application.routes.draw do
  root to: 'rps#index'
  get 'rps/new'
end
```

まずフォームからajaxリクエストを送信するところ｡

``` ruby:index.html.erb
<div>
  じゃんけん･･ <span id = 'shape'> <%= @shape %></span>
</div>

<br />

<%= button_to 'ぽん！', rps_new_path, method: :get, remote: true %>
```

ここから飛んできたリクエストを受けるサーバー側としては･･

```ruby:controllers/rps_controller.rb
class RpsController < ApplicationController
  def index
    @shape = shape
  end

  def new
    respond_to do |format|
      format.json {
        render json: {shape: shape}, status: 200
      }
    end
  end

  private

  def shape
    ['✊', '✌️', '✋'].sample
  end
end
```

jsにマルチバイト文字を投げるとかアレですが､手抜きサンプルということで･･･(ゲホゲホ)

こんな画面になりました｡

<img width="158" alt="スクリーンショット 2017-07-09 0.06.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/151343/3cccb9d3-ad4d-6f50-5562-faa5c480495e.png">


chromeの検証画面で見てみると･･･

![responce.gif](https://qiita-image-store.s3.amazonaws.com/0/151343/64d4f4f3-665f-5469-8be4-c0c4fe47bf9f.gif)

ふむふむ｡
押すたびにJSONが帰ってきているよね｡

## ハマる

さて､あとは飛んできたJSONをjsでキャッチして画面を差し替えるだけ｡

たしかこんなでしたよね〜｡

```js:rps.js
function set_ajax() {

  $('#ajax-btn').on('ajax:success', function(event, data, status, xhr) {
    $('#shape').text(data['shape']);
  });

  $('#ajax-btn').on('ajax:error', function(event, xhr, status, error) {
    alert("失敗！");
  });
}

$(document).on('turbolinks:load', set_ajax);
```

ところが･･･ウンともスンとも言ってくれない｡

```
Uncaught TypeError: Cannot read property 'shape' of undefined
```
んにゃ？

concole.logで､`data`の中身を見てみると･･やっぱり`undefined`だよこれ｡
ナヌ｡

[jquery-ujsの公式wiki](https://github.com/rails/jquery-ujs/wiki/ajax) を読んでも､『引数は `[event, xhr, status, error]` が取れる』 ということがしっかり書いてあるんだけどな･･･｡

このあとturbolinksを切ったりgemを入れ替えたりだとか､散々な長旅を経た結果､[ようやくいいドキュメントを発見しました｡](http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html#dealing-with-ajax-events)

rails5.1からjQuery依存を捨てたことによってもろもろ変更があったらしい｡

<img width="667" alt="スクリーンショット 2017-07-09 0.45.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/151343/9cd35e2d-b30b-8663-83cb-76464173b7d3.png">

つらつら読んでいくと･･･

```
all custom events return only one parameter: event. 
In this parameter, there is an additional attribute detail 
which contains an array of extra parameters.
```

*カスタムイベントは全て1つのパラメータ`event`だけを返します｡パラメーターの中には､他のパラメーターを含む配列をふくむ属性`detail`が入っています*


おおおおーこれか｡
そりゃ余計な引数をつけたら`undefined`になるわけだorz

というわけで､

```js:rps.js
function set_ajax() {

{
  $('#ajax-btn').on('ajax:success', function(event) {
    data = event.detail[0];
    console.log(data);
    $('#shape').text(data['shape']);
  });

  $('#ajax-btn').on('ajax:error', function(event) {
    alert("失敗！");
  });
}

$(document).on('turbolinks:load', set_ajax);
```

こういう風に変えたら

![Janken.gif](https://qiita-image-store.s3.amazonaws.com/0/151343/302a7525-d4fc-79fc-7d8a-44ec9f3ff510.gif)

･･･ど､どや❗ajaxカンタンだろ？ (虫の息)

## 感想

手抜きアプローチを選択したはずが時間がかかってしまいました｡

#### rails-ujs依存というツラミ

  * このアプローチだと`rails-ujs`依存というツラミがいつまでも残るようなモヤモヤ感がのこります｡仕様がまた変わるとか､サポートされなくなったりすると､影響をもろに受けそう｡

#### じゃあどうするの？
  * モダンなjsフレームワークを使わないのであれば､自分で `$.ajax()...` を呼んでajaxイベントを発火させるのが最も無難なオプションでしょうか｡
  *  いずれにせよ､フロント周りはrailsと切り離すというのが健全なアプローチ､といういつもの話に収束するネタなのでしょうか｡

巷では割りと話されている感のある話題ですが､これだという資料が見つけられなくて困りました｡
みなさんはどうされていますか？