ボクココ

個人開発に関するテックブログ

rails-ujs と form_with の使い方

ども、@kimihom です。

Rails 5.0 までは jquery-rails を使ってフォームやリンクの Ajax 通信を可能にしていたけど、Rails 5.1 からは rails-ujs として切り出され、晴れて jQuery からの脱却を可能にした。

そこで、本記事ではこの rails-ujs と関連深い form_with の使い方や注意点についてまとめる。

rails-ujs

rails-ujs は、Ajax の送受信の "送" の部分を JavaScript で実装せずに、よしなにやってくれるライブラリだ。私たちは Rails 5.1 から導入された form_with を使ってフォームを構築し、その form でデータを送った後のレスポンスのハンドリングだけを JavaScript で書けば良くなる。

rails-ujs を使わない場合は、フォーム送信ボタンを押した時のイベントをハンドリングし、各フォームの値を詰めて Ajax で送るみたいな実装が必要になる。それらの実装を全部 rails-ujs と form_with が肩代わりしてくれると考えるといいだろう。実際は form_with だけでなく、View の button_to や link_to などに remote: true をつけるだけで Ajax 化することもできる。

rails-ujs の魅力てのは、やはり Rails のレールに乗ることができることだ。お分かりの通り、フォームの実装はフロントエンドの鬼門だ。各種項目のバリデーションや 各項目のI18n、デフォルト値の設定、値の送信など必要なことがたくさんある。これら実装を React や Angular で実装してもいいだろうけど、それらを使う場合には先の実装を全部 フロントエンドフレームワーク側に寄せる必要が出てきてしまう。しかし、実際には Rails 側のデータベースのモデルと関連づけることで、フォームの実装はより手軽に、より密で効果的に実装が可能である。

rails-ujs のインストール

さて、そんな素敵な rails-ujs を早速インストールしよう。Rails 5.1 ではデフォルトで rails-ujs が入っているので、require するだけだ。

$ cat app/assets/javascripts/application.js

//= require rails-ujs

これで読み込みが完了する。

form_with

Rails 5.1 から導入された form_with であるが、今までの form_for と同じように考えると手痛い思いをすることがあるので気をつけよう。

form_with のデフォルトは remote: true の状態である

form_with の場合、何もオプションを指定しなくても Ajax 送信となる。そのため、form_with でフォームを作って submit した時に何も動作しない?ってなることだろう。一般的なページ遷移するフォーム送信をしたい場合には local: true を明示する必要がある。

form_with で構築した form や text_field には id などが付与されない

これは大きな変化なので注意が必要だ。例えば以前のバージョンで form_for を使って構築していた場合、勝手に form や text_field にはモデルの属性名などに応じて id が割り振られていた。この id をベースに JavaScript 側で処理を実装していた方もいることだろう(私もそうだった)。

しかし、form_with で構築した form や text_field には id や class などが自動で生成されない。そのため、明示的に id: "name" のように割り当てる必要がある。てことで、各フォーム項目に明示的に id や class を割り当てたあと、JavaScript 側で処理しているコードがある場合は修正する必要がある。

rails-ujs の使い方

さて、ここからが rails-ujs の出番だ。フォームを送った後のイベントを定義してあげよう。そのためには、送り元の <form><a> タグから発火されるイベント ajax:success を定義してあげれば良い。

Controller と View, そして JavaScript の関連を知る必要があるので、以下にまとめて紹介する。本来なら jQuery 使う必要がないけども、今までの差分って意味で jQuery で実装した場合でコードを記す。

class UsersController < ApplicationController

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_param)
    if @user.save
      render json: {result: "ok", user: @user}
    else
      render json: {result: "ng", msg: @user.errors.messages}
    end
  end

  private
  def user_param
    params.require(:user).permit(:name)
  end
end
<%= form_with model: @user, url: users_path, id: "user-create-form" do |f| %>
  <%= f.text_field :name %>
  <%= f.submit %>
<% end %>
$(document).on("ajax:success", "#user-create-form", function(e) {
  console.log(e.detail[0]);
});
$(document).on("ajax:error", "#user-create-form", function(e) {
  console.log(e.detail[2]);
});

JavaScript 側で Ajax を送る必要がなく、終わった後の UI の変更だけをすればいいのでフロント側で書かなければならないコードが減っているのがおわかりいただけたことだろう。 rails-ujs からの変更点として何よりも大事なのが、ajax:success 時の処理だ。コールバック関数の引数は1つにまとめられ、Ajax 送信した後のレスポンスは、e.detail[0] に格納されている。この保存場所はちょっとわかりづらい気もするけど、rails-ujs ではそういう実装になっているので従うしかないだろう。ajax:error 時には detail[2] に status や statusText が格納されている。ここら辺は console.log で出力しながら確認するといい。

ちなみに、フォーム送信前に何かしらチェック処理を挟んでエラーの場合は送信キャンセルをしたいこともあるだろう。その場合は、ajax:beforeSend ではなく、ajax:before 内で return false; をしてあげると実装できる。ajax:beforeSend もあるんだけど、送る直前に呼ばれるコールバックなので return false しても Ajax 通信が走ってしまう。

JavaScript コードから form を明示的に送信したい場合

今までの話は、ユーザーが submit を HTML 上でクリックなり Enter してくれた場合に動くというシンプルなケースだった。しかし、実際には JavaScript 側でごにょごにょしてから JavaScript 側で form 送信をしたいことが出てくるはずだ。

その時に、何も考えずに JavaScript 側から submit() を呼ぶと、Ajax 送信ではなく普通のフォーム送信となってしまう 問題を見つけた。ページ遷移してしまうのである。これに対して JavaScript 側からの submit() を Ajax で送信したい場合にどうすればいいかググったが全く出てこないので、仕方なくソースコードを読んだ。

rails-ujs には Rails.fire というメソッドが提供されており、ここで submit イベントを発火させることで対象フォームを JavaScript からリモート送信することが可能になるようだ。

Rails.fire($("#user-create-form")[0], "submit");

rails_ujs の考え方

今回の例は 純粋にレスポンスを json で返して JavaScript 側でその後の UI 処理を任せる方法だったけど、もっと簡単な方法として create メソッドのレスポンスを json ではなく html.erb を返してあげて、JavaScript 側で一括でレスポンスの HTML へ書き換える方法がある。この方法はかなりおすすめで、ちゃんとやれば JavaScript 側で書かなければならないコードを激減させることができる。

Ajax 終わったあとちょっとしたメッセージ出したい程度だったら JSON、がっつり HTML を書き換えたい場合には HTML を返すって思っておくと、今後の実装が楽になることだろう。

HTML を Ajax のレスポンスとして返した場合は、e.detail[2].response に HTML が入っている。

終わりに

Rails 5.1 から導入された form_with だが、以前の form_for と同じ感じで使うとつまづく点が出てくるので気をつけよう。私が遭遇した問題は上記で全てだが、他に問題が起きた際には yarn から落としてきた node_modules/rails-ujs/lib/assets/compiled/rails-ujs.js のソースを読むことになるだろう。そこまでコード量は多くないので、ググって詰まるより実際にソースを読むことをお勧めする。

そんなわけで快適なレールに乗った Rails プログラミングをこれからも楽しんでいこう!