(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

Railsでフォームを非同期で送信して更新処理を行う

タイトルママです。
Railsアプリを書いていて、画面遷移を起こさずに更新処理を行って画面にも反映させたかったのでざっくりやってみた。*1

例として適当に簡単なアプリケーションを作りたいと思います。

rails new test-app
rails g model user name:string age:integer
rake db:migrate
rails g controller users index create

ユーザーを追加して一覧表示するだけのアプリです。超簡単。
ユーザーは名前と年齢の項目を持っています。コントローラは一覧表示用にindexアクションとユーザーの追加用にcreateアクションでいいでしょう。

あと、個人的な好みでhamlを使います。

#Gemfile
gem 'haml-rails'

あくまでも僕の好みなので、すっ飛ばしても構いません。

次にconfig/routes.rbを弄くります。コントローラを作った時にできていた2行は消して、次の一行を足してRESTっぽい感じにしたいと思います。

#config/routes.rb
resources :users, only: [:index, :create]

さて、まずはUsersController#indexから

#users_controller.rb
def index
  @all_users = User.all
end

普通に全取得して、画面に渡すだけ。

そしたら次はindex.html.hamlかな。

-# index.html.haml
%h2 ユーザー一覧
%ul{id: 'user_list'}
  - @all_users.each do |user|
    %li= "#{user.name}, #{user.age}"

= form_tag '#', {id: 'user_form', onsubmit: 'return false;'} do 
  .field
    名前
    = text_field_tag 'user[name]'
  .field
    年齢
    = text_field_tag 'user[age]'
  = button_tag 'ユーザーを追加する', {id: 'user_add_button', type: 'button'}

ほい。こんな感じでいいでしょう。
form_tagのremoteオプションでの非同期処理はcallbackの受け方が分からなかった*2ので今回は使わないです。あと、テキストフィールドにフォーカス当たっている時にEnterでsubmitするのは今回邪魔なんで消しています。で、submitを使うと困ったことになるので、button_tagで代用します。*3

次に実際に非同期処理を行うjQueryの方を作りこみましょう。

#users.js.coffee
$ -> init()

init = ->
  $user_list = $('#user_list')
  $user_add_button = $('#user_add_button')
  $user_add_button.on 'click', ->
    $user_name = $('#user_name')
    $user_age = $('#user_age')
    _name = $user_name.val()
    _age = $user_age.val()

    _data =
      'user' :
        'name' : _name
        'age' : _age

    $.ajax
      type : 'POST'
      url : '/users'
      dataType : 'json'
      data : _data
    .done (user) ->
      $user_list.append("<li>"+user.name+", "+user.age+"</li>")
    .fail ->
      alert("進捗ダメです")

_dataのとこでJSONオブジェクトを組み立てます。この構造地味に大事です。あとはいいかな。処理が正常に終了した場合、jsonオブジェクトを受け取り、そこから名前と年齢を取得しています。ダメだったらalertで「進捗ダメです」と教えてくれます。

さて、最後。UsersController#createですね。

#users_controller.rb
def create
  user = User.create(user_params)
  render json: user
end

private
def user_params
  params.require(:user).permit(:name, :age)
end

ちゃんとStrong Parametersを使っているあたりえらいですね。これだいぶ適当に書いているのでこんなんですけど、ちゃんとエラー処理とかもいれましょうね。

ざくっと書きましたけど、こんな感じでRailsアプリケーションで非同期処理ができました!
もう少しスマートな方法あるといいんだけど、非同期処理のcallbackを受けたいし、DOMも弄りたいしってなるとこれしかないのかなーとか思います。

*1:@さんに色々教えてもらったお陰でだいぶjQueryが分かるようになった

*2:そもそも受けれるか分からない…

*3:formのsubmitを使わないってアンチパターンな気もするけど…。