Rack バージョンに応じて HTTP 422 のシンボルを切り替えるスキャフォールド生成
Rack v3.1.0 で HTTP ステータスコード 422 のシンボルが :unprocessable_entity から :unprocessable_content に変更されたことを受け、jbuilder のスキャフォールドジェネレーターが Rack バージョンを実行時に検出して適切なシンボルを選択するようになりました。
背景
Rack v3.1.0 において、IANA の HTTP Status Code Registry に準拠する形で HTTP 422 に対応するシンボルが変更されました。これは rack/rack#2137 で行われた変更で、Rack::Utils::SYMBOL_TO_STATUS_CODE のマッピングが更新されています。この影響を受け、Rails 本体も rails/rails#53383 で同様の対応を行っており、jbuilder のスキャフォールドもこれに追従する形で本 PR が作成されました。
jbuilder は gemspec 上 actionpack(action_dispatch)に依存していないため、ActionDispatch::Constants::UNPROCESSABLE_CONTENT を利用することができません。そのため、どちらのシンボルを使うかの判断を Rack 自身の SYMBOL_TO_STATUS_CODE テーブルから直接引き出す方式が採用されています。
技術的な変更
ジェネレーターヘルパーに status_unprocessable_content メソッドを追加し、スキャフォールドテンプレート内でハードコードされていた :unprocessable_entity をこのメソッドの返り値に置き換えています。
lib/generators/rails/scaffold_controller_generator.rb に追加されたメソッドは以下のとおりです。
def status_unprocessable_content
::Rack::Utils::SYMBOL_TO_STATUS_CODE.key(422) rescue :unprocessable_content
end
Rack::Utils::SYMBOL_TO_STATUS_CODE.key(422) は Rack のバージョンによって次のように異なる値を返します。
- Rack 3.1 以上:
:unprocessable_content - Rack 3.0 以下:
:unprocessable_entity
rescue :unprocessable_content は、万一 SYMBOL_TO_STATUS_CODE が存在しない環境でもフォールバックできるようにするためのガードです。
コントローラーテンプレート側では、controller.rb と api_controller.rb の両方で create および update アクションのエラー時レスポンスが次のように変更されました。
変更前(controller.rb より抜粋):
format.html { render :new, status: :unprocessable_entity }
format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
変更後:
format.html { render :new, status: :<%= status_unprocessable_content.to_s %> }
format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :<%= status_unprocessable_content.to_s %> }
テストコードも Rack のバージョンを Gem::Version.new(Rack::RELEASE) で比較し、期待するシンボル文字列を分岐させる形に更新されています。これにより、Rack 3.0 以下の環境でも 3.1 以上の環境でも CI が正しく通ることが保証されます。
設計判断
実行時の Rack バージョン検出 という方式が採用されました。ジェネレーターが実行される時点での Rack バージョンを参照するため、コードに Rack バージョン番号を埋め込む必要がなく、将来の変更にも対応しやすい設計です。
SYMBOL_TO_STATUS_CODE のリバースルックアップ(.key(422))を利用することで、Rack 自身が「422 の正式なシンボル」として何を定義しているかを直接取得しています。これは Rack の公開メソッドではなく内部テーブルを参照する方式ですが、PR 内では actionpack への依存を追加せずに済む点が優先されました。rescue によるフォールバックでその脆弱性を補っています。
まとめ
本 PR は、jbuilder が生成するスキャフォールドコードを Rails 本体の挙動に追従させる小さな変更ですが、Rack バージョンをジェネレーター実行時に動的に検出することで、Rack 3.0 以下との後方互換性を保ちつつ Rack 3.1 以上での正しいシンボルを使用できるようにしています。依存関係を増やさずに互換性を実現した判断は、ライブラリのメンテナンスコストを抑える観点でも参考になります。