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ステータスコードです。