Rails3.1でAjaxを使う

よく理解できていなかったのでチュートリアル的に整理した。

まずはチュートリアル用のアプリを新規作成して、コントローラーを一つ作る。

rails new ajax_tutorial
cd ajax_tutorial
rails g controller sandbox index update_time

Ajaxなフォームやリンクを作成する

form_forやlink_toといったヘルパーのオプションに:remote => trueを加えるとボタンを押した時やリンクをクリックした時のリクエストが非同期リクエストになる。

app/views/sandbox/index.html.erb
<h1>Sandbox#index</h1>
<%= link_to('update time', {:action => 'update_time'}, 
  :id => 'update-time-link', 
  :remote => true, 
  :confirm => '本気ナリか?') %>
<div id="time"></div>

:confirmオプションを指定するとリクエストを送信する前に確認のダイアログが出る。

なおRails2時代のlink_to_remoteやremote_form_forといったメソッドはなくなっている。

JavaScriptテンプレートを準備する

非同期で実行されるアクションに対してJavaScriptのテンプレートを用意しておけば、その内容がレスポンスとして返され、DOMの操作等の処理を反映させることができる。

app/views/sandbox/update_time.js.erb
$('#time').empty();
$('#time').append('<%= Time.zone.now %>');

JavaScriptテンプレートはaction_name.js.erbのようなファイル名で保存する。
拡張子はjs.hamlやcoffeeといったものでも可能。
coffee.erbという拡張子は不可だが、拡張子coffeeのファイルでerbとしても処理される。

動作確認

Railsサーバーを起動して

rails s

以下のURLにアクセス。

http://localhost:3000/sandbox/index

リンクを押すたびにAjaxで現在の時間が表示されるようになった。

フォームやリンクにコールバックをバインドする

さらなるステップ。
非同期処理を開始したときにインジケータを表示したり、完了した時にそれを隠したりといった処理のためにフォームやリンクに対してコールバックを設定できる。

コントローラーごとにCoffeeScriptのファイルが作られているので、これを編集する。
CoffeeScriptが嫌な人は普通のjsにリネームしてJavaScriptを書いてもオッケー。

app/assets/javascripts/sandbox.js.coffee
$(($)->
  $('#update-time-link')
    .live("ajax:complete", (xhr)->
      $('#time').append('<div>Done.</div>')
    )
    .live("ajax:beforeSend", (xhr)->
      $('#time').append('<div>Loading...</div>')
    )
    .live("ajax:success", (event, data, status, xhr)->
      $('#time').append('<div>Yeah!</div>')
    )
    .live("ajax:error", (data, status, xhr)->
      alert("failure!!!")
    )
)

ajax:successのdataにはJavaScriptテンプレートの出力内容が入っている。
JavaScriptテンプレートを使うのならばその中で処理を行っていると考えられるので、dataを使うような処理はここでは必要ないはず。

以上がRailsアプリでAjaxを使う基本の流れになる。

JavaScriptテンプレートを使わないケース

ここからはおまけのステップ。

JavaScriptテンプレートは作らずに、コントローラーからはJSONを返してJavaScriptのコールバック内にいろいろ書いて処理する、というやり方もできる。

app/controllers/sandbox_controller.rb
class SandboxController < ApplicationController
  def index
  end

  def update_time
    render :json => {:time => Time.zone.now}
  end
end
app/assets/javascripts/sandbox.js.coffee
$(($)->
  $('#update-time-link')
    .live("ajax:complete", (xhr)->
      $('#time').append('<div>Done.</div>')
    )
    .live("ajax:beforeSend", (xhr)->
      $('#time').append('<div>Loading...</div>')
    )
    .live("ajax:success", (event, data, status, xhr)->
      $('#time').append(data.time)
    )
    .live("ajax:error", (data, status, xhr)->
      alert("failure!!!")
    )
)

dataにJSONが入って返ってくる。

どちらで行くかは状況とお好み次第で。
個人的にはJavaScriptテンプレート使うよりこっちの方が処理の見通しが良くていい気がする。

参考