おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

turbolinksとform_withを便利に使うためのgemを作った

前提

rails標準のわりに使っている人の少ないturbolinksですが、僕は便利に使っています。turbolinksはご存知の通り、リンクを全部ajaxリクエストに置き換えてページ遷移を早くするライブラリです。

turbolinksが実現している「リクエストは全部ajaxにして、フルのページ遷移を避けたほうが速い」というロジックはformでも同じことが言えます。

Railsは5.1からform_withというviewヘルパーが増えました。これは既存のform_tagform_forを統合するヘルパーという位置づけですが、それ以外に大きな違いとしてデフォルトでdata-remote="true"がformタグに付与される、というのがあります。この振る舞いはformもturbolinksみたいにajaxにしようぜ!というDHHの思想の現れなのだろう、と推測します。実際、form_withのPRでDHHがそんなようなことをコメントしていますね。

Make remote: true the default. Full-page changes after submissions are rough. When using Turbolinks, a normal redirect will generate a Turbolinks.visit() call, and otherwise there's SJR. (We could consider having config.action_view.forms_remote_by_default that you could set to false, for people going old school).

turbolinksはajaxを利用したform submitの一部に対応しています。具体的にはturbolinks gemはredirect_toの挙動を差し替えており、form submitによるajaxリクエストをしたときのリダイレクト処理を非ajax時と同じ*1になるようにしています。書き換えたredirect_toメソッドは、もしget以外のajaxリクエストから呼び出された場合、302を返さずに、クライアントサイドでTurbolinks.visit()という画面遷移用のjs関数を実行するためのレスポンスを返します。この結果、ユーザとしては単にformをsubmitしてからredirect_toしたのと同じような画面遷移になります。

先程「turbolinksはajaxを利用したPOSTリクエストの一部に対応しています」と書きました。つまり対応していない部分があるということです。DHHが書いたコメントにもありますが、バリデーションエラーになった際の対応は、SJR(Server-generated JavaScript Responses)などを利用して各自でエラーメッセージを表示する処理を書かなければなりません。このあたりもうちょっとうまくできるとturbolinksもう少し普及するんじゃないのかな、という気がします*2

最初はSJR部分を普通に書いていたのですが、統一的なインタフェースがあったほうが良さそうだな、と思ったのでgemにしてみました。

willnet/ajax_error_renderer: a validation error renderer for ajax request

ajax_error_rendererとは

render_ajax_errorという、ajaxでエラーメッセージを表示するメソッドを提供する小さなgemです。次のようにAjaxErrorRendererをincludeすると使えるようになります。

class ApplicationController < ActionController::Base
  include AjaxErrorRenderer
end

ajaxでリクエストが来てエラーメッセージを表示する場面でrender_ajax_errorメソッドを使います

class UsersController < ApplicationController
  def create
    @user = User.new(params.require(:user).permit(:name))
    if @user.save
      redirect_to users_path, notice: 'You created a user successfully!'
    else
      render_ajax_error model: @user
    end
  end
end

すると#errorなDOMに動的にエラーメッセージが表示されるようになります

<%= form_with(model: @user) do |form| %>
  <div id="error">
    <%# ここにエラーメッセージが表示される %>
  </div>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

基本的な使い方は以上です。簡単ですね。どうぞご利用ください。

*1:ユーザ体験的な意味で。実際の挙動は異なります

*2:とはいえ公式でこう!というように対応するのも難しい、というのもわかる