Rails で ajax を始めるときに理解しづらい点と、jQueryを使わず Vue.js を使う方法の整理


Rails 5.2.4.1で動かしています。6系でもたぶん一緒

基本的なことは Rails ガイドを参照していきます。
https://railsguides.jp/working_with_javascript_in_rails.html

respond_to に、format.js が必要

abc_controller.rb
def update
  :
  respond_to do |format|
    if ovf.update(params[:id])  then
      format.html { redirect_to polymorphic_url([ovf]), notice: '更新されました。' }
      format.js
      format.json { render :edit, status: :created, location: @ovf }
    else
      :
    end
  end
end

Railsガイドによると

format.jsがrespond_toブロックの中にある点にご注目ください。これによって、コントローラがAjaxリクエストに応答できるようになります。

とのこと。

Controllerが (1) jsを返す方法と、(2)JSONを返す方法がある

(1) update.js.erb が必要

Railsガイドによると format.js に対応する事に続けて、次の記述がある。

続いて、対応するapp/views/users/create.js.erbビューファイルを作成します。実際のJavaScriptはこのビューで生成され、クライアントに送信されてそこで実行されます。

上記の Controller では update アクションから呼んでいるので、 update.js.erb が必要となる。これはJSを返す方法にあたる。Controller が、format.js で指定された JS ファイルを読み込んで、それをクライアント(ブラウザ)へ送って、JSが実行される。

Railsガイドでは「create.js.erbビューファイルを作成します」とあるが、partial も指定できる。

  format.js {render partial: "reload" }

これで
create.js.erb の代わりに _reload.js.erb が読み込まれる。partial なのでアンダースコアに注意。

(2) app/assets/javascripts/application.js にAjaxを書く

application.js ではなく、分割した別ファイルにする場合も同様。
Railsガイドには記載がない。

この方法の場合、(1) に書いたJSの記述は app/assets/javascripts 配下の.js ファイルに移動する。CoffeeScript でもOK。

Controller は JSON を返すように変更する。

 format.js{ render json: {ovf: @ovf}, status: :ok }

これで update.js.erb や partial で生成されたJSではなく、json で返すように変わる。

このJSONを使うように、app/assets/javascripts で JavaScript を書いておく必要がある。どう書くかについては、Railsガイド3.5 rails-ujsのイベントハンドラに記載がある。

3.5 rails-ujsのイベントハンドラ

Railsガイドの例では、次のように記載がある。

document.body.addEventListener('ajax:success', function(event) {
  var detail = event.detail;
  var data = detail[0], status = detail[1], xhr = detail[2];
})

上記によって、event.detail[0] から、Controller が返した JSON を取得できる。

turbolinks を使っている場合は、Railsガイド 5.2 ページ変更イベント に記載があるように、 turbolinks:load を使う。

function set_ajax() {
  document.body.addEventListener('ajax:success', function(event) {
    var detail = event.detail;
    var data = detail[0], status = detail[1], xhr = detail[2];
    alert("Success!" + data + status + xhr);
  });
  document.body.addEventListener('ajax:error', function(event) {
    alert("fail");
  });
}
document.addEventListener("turbolinks:load", set_ajax );

疑問:format.json はなんだ?

Controller には、次のような記述もある。scaffold で生成した場合についてくる。

 format.json { render :edit, status: :created, location: @ovf }

view から data-remote: true した場合は、 format.js{} が反応しているので、 format.json{} は使われていない。

リクエスト時にJSONフォーマットを指定してくるAPIや、独自のJSがJSONでリクエストしている場合は必要。なければ不要なので消しておく。

cf.
Let'sプログラミング JSON/XML形式で出力する では、
http://localhost:3000/movies/index.json のようにJSON形式を明示的にリクエストした場合に、 format.json が使われると記載されている。

その他

escape_javascript は j で書ける

<%= escape_javascript(render: @user) %>

の代わりに

<%= j(render: @user) %>

となる。

form_with

オプション local: true を指定しない。デフォルトでは入らなくなっているので、そのままでよい。

= form_with(model: @ovf, url: url, method: method ) do |f|
  = f.submit(I18n.t('..button'), data: { "disable-with": "updating" })

$('#something') の記述を避ける

jQuery をやめようと思うので、 $(document)$('#something') を避ける。参考になるドキュメントが一気に減ってしまう。

Vue でバインディングする

こちらを参考にした。

Rails + Ajax で Vue.js のバインディングを利用する
https://qiita.com/NaokiIshimura/items/c7c1330743757925e385

app/views/../scripts/_vue_test.html.haml
%p#script_update {{ message }}

:javascript
  var script = new Vue({
    el: '#script_update',
    data: {
      message: 'default message should be ID',
    },
  });

%p#script_update には、"default message should be ID" が表示されている。
Controller で生成した ovf を local 変数(同名のovf)として_reload.js.erb へ渡し、値をVueで定義した script.message へ入れる。

controller#update
 format.js {render partial: "reload", locals: {ovf: ovf },status: :ok }
app/views/../_reload.js.erb
script.message = <%= ovf.script %> ;

この方法がVueにレンダリングを任せられるので、楽そうな印象。

  • data-remote: true を使って、Rails 標準のままrequestできる。
  • Rails が js を返せる限りは動く。
    • addEventListener が不要
  • ただし、JSON を受け取る必要もない代わりに、View 内の Vue に密結合する。
    • Vueがシンプルなうちは、Controller から view を意識している程度ではある

Vue で XHR

VueそのものはXHRを扱えない。他のライブラリと組み合わせる必要がある。

jQuery ではない方法では axios がある。Rails 標準の data-remote: true を使う場合は、追加のXHRは不要。
https://github.com/axios/axios

Rails の data-remote:true を動かす

Vue methods から submit するときは、data-remote が効かない。代わりにRails.fire を使うとよい。(が、公式ドキュメントが分からない)

  checkbox_and_so_on: function(event){
    // event.target.form.submit()
    Rails.fire(event.target.form, 'submit')
  }

cf. rails-ujs と form_with の使い方
https://www.bokukoko.info/entry/2017/12/22/194327

Vue で押さえておきたいこと

今からVue.jsを始める人のための「知るのを後回しにしてよい」n個のこと
https://qiita.com/fruitriin/items/3249bb24d60932bb42ee