Rails5.1でajaxができない?
ajaxのやり方を聞かれたのでRails4時代の知識をドヤ顔で答えていたら、すっかりやり方がかわっていて浦島太郎感が半端ないです。
(react? vue.js? なにそれ)
そもそも『rails5.1 + jQuery(rails-ujs)でajaxをやる』という路線自体が、急速に消滅に向かっている空気を感じるので、最新の情報が全く見つからなかったです。トホホ。
結論
1. jquery-ujsの代わりに、rails-ujsを使おう
5.1からjquery依存が廃止されたのに伴い、ujs関連はrails-ujsとして切り出されたようです。
gem 'rails-ujs'
//= 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ともにクラシックな記法で書いていきます。
Rails.application.routes.draw do
root to: 'rps#index'
get 'rps/new'
end
まずフォームからajaxリクエストを送信するところ。
<div>
じゃんけん・・ <span id = 'shape'> <%= @shape %></span>
</div>
<br />
<%= button_to 'ぽん!', rps_new_path, method: :get, remote: true %>
ここから飛んできたリクエストを受けるサーバー側としては・・
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にマルチバイト文字を投げるとかアレですが、手抜きサンプルということで・・・(ゲホゲホ)
こんな画面になりました。
chromeの検証画面で見てみると・・・
ふむふむ。
押すたびにJSONが帰ってきているよね。
ハマる
さて、あとは飛んできたJSONを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 を読んでも、『引数は [event, xhr, status, error]
が取れる』 ということがしっかり書いてあるんだけどな・・・。
このあとturbolinksを切ったりgemを入れ替えたりだとか、散々な長旅を経た結果、ようやくいいドキュメントを発見しました。
rails5.1からjQuery依存を捨てたことによってもろもろ変更があったらしい。
つらつら読んでいくと・・・
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
というわけで、
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);
こういう風に変えたら
・・・ど、どや❗ajaxカンタンだろ? (虫の息)
感想
手抜きアプローチを選択したはずが時間がかかってしまいました。
rails-ujs依存というツラミ
- このアプローチだと
rails-ujs
依存というツラミがいつまでも残るようなモヤモヤ感がのこります。仕様がまた変わるとか、サポートされなくなったりすると、影響をもろに受けそう。
じゃあどうするの?
- モダンなjsフレームワークを使わないのであれば、自分で
$.ajax()...
を呼んでajaxイベントを発火させるのが最も無難なオプションでしょうか。 - いずれにせよ、フロント周りはrailsと切り離すというのが健全なアプローチ、といういつもの話に収束するネタなのでしょうか。
巷では割りと話されている感のある話題ですが、これだという資料が見つけられなくて困りました。
みなさんはどうされていますか?