Turbolinksからturbo-railsに変更する際のformのerrorの出し方

目次

マーケター、エンジニアを月1時間からジョインできるプラットフォーム

airteamは月1時間からマーケターやエンジニアに相談できるプラットフォーム。 雇うのはハードル高いけどプロをチームに入れたい。そんな経営者のためのサービスです。 相談にのる方も募集しています。

タスクなしだから月一時間からジョイン可能

作業はなくオンライン相談メイン。 月1時間からさっと経験者に継続的に相談できます。

多様な経験者を雇用するより何倍も早くチームに

あらゆるジャンルの経験者がいるので あなたのチームのノウハウの選択肢が広がります。

NDAはすでに締結済み、契約もスムーズ

契約の煩雑なやりとりはなく、NDAはすでに締結済み、書面のやりとりはありません。

Rails6からRails7に移行する際にTurbolinksからturbo-railsに変更したのですが、それによりformのerrorが出なくなりました。今回はその理由と対策についてまとめます。

なぜturbo-railsに変更するとerrorが出ないか

Rails 7系ではTurbolinksに代わりTurbo (Hotwire)がデフォルトで導入され、フォーム送信後の挙動が変わりました。Turbo Driveはフォーム送信後、通常はサーバーから303リダイレクト(成功時)されることを想定しています。一方、バリデーションエラーなどでフォームを再表示する場合、Rails 6まではそのまま新しいページHTMLを200 OKで返していました。しかしRails 7 + Turboでは、単に200 OKでrenderするとページの再描画が行われず、ユーザー側でエラーメッセージが表示されない現象が起こります。

これはブラウザが持つPOST再送信時の挙動(「再送信しますか?」ダイアログ)を避けるために、Turboが通常の200応答での再描画を許可していないためです​。つまり、Turbo導入後はフォーム送信=リダイレクトが前提となり、従来のようなその場での再描画には追加対応が必要になります。

renderでなく、ページ自体にリダイレクトするredirect_toなら起きません。

errorを出すための方法

errorをturbo-railsで出すための方法は下記

  • form送信においてturboを無効化する(form_withにオプションでdata: { turbo: false }を付与
  • コントローラ側で適切なステータスコードを返すようにします。具体的には、作成失敗時のrenderにHTTP 422 (Unprocessable Entity)を付与します。

作成失敗時のrenderにHTTP 422 (Unprocessable Entity)を付与

  • ステータスコードの指定: バリデーション失敗時のrender 'new'status: :unprocessable_entityを付与します。これによりTurbo経由のフォーム送信でもページが再描画され、エラーメッセージの部分テンプレートが表示されるようになります。Rails公式ガイドでも、失敗時は422ステータスで再レンダリングする方針が示されています。
  • フラッシュメッセージの即時表示: 失敗時にflash[:alert]を設定している場合は、flash.now[:alert]に変更します。こうすることで、レンダリング直後のビューでフラッシュメッセージが表示されるようになります​。
def create
  @boshu = Boshu.new(boshu_params)

  if @boshu.save
    # 成功時は303 (See Other) でリダイレクト(Rails 7推奨)
    redirect_to boshus_path, notice: "登録できました!", status: :see_other
  else
    # バリデーションエラー(作成失敗)の場合

    # 今回のレンダリングでだけ表示したい場合はflash.nowを使う
    flash.now[:alert] = "登録できませんでした!"

    # renderのステータスを422 (Unprocessable Entity)にする
    render :new, status: :unprocessable_entity
  end
end
  • 成功時: Turbo推奨の redirect_to … , status: :see_other(HTTP 303) で画面遷移。
  • 失敗時: render :new, status: :unprocessable_entity を行い、同じ画面をエラー付きで再表示。
<% if model.errors.any? %>
  <div class="error_messages">
    <h2><%= model.errors.count %> 件のエラーが発生しました。</h2>
    <ul>
      <% model.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

<%= form_with(model: @boshu, local: true) do |form| %>
  <%= render 'layouts/error_messages', model: form.object %>
  <!-- フォーム本体 -->
<% end %>

renderではなくredirect_toに変更

redirect_toにすればページ自体再描画されるのでエラーも表示されますが、投稿失敗時にはformの値の保持などからrenderで一部分変更が推奨されています。

HTTPステータス422(Unprocessable Entity)とは?

422 Unprocessable Entity は、HTTPステータスコードの一つで、「サーバーはリクエスト自体(構文・文法)は正しく理解したが、その内容が処理できない」
ことを示します。

なぜ 400 Bad Request でなく 422 Unprocessable Entity なのか?


400 Bad Request は「リクエストの構文が不正で、サーバーが解釈できなかった」という趣旨です。今回のケース(バリデーションエラー)は「リクエストの形式や文法は正しいが、内容が要件を満たしていない」ため、厳密には 400 とは意味が異なります。そこで、WebDAV で拡張された 422 が「妥当な形式だが処理不可」という状況を表すステータスとして使われます。

status: :see_other(HTTP 303)とは?

status: :see_other(HTTP 303 See Other) とは、「リソースは別の場所にあり、クライアントは GET メソッドでそこへアクセスせよ」という内容を示すHTTPステータスコードです。