何が変わったか
Rails 8.1 への移行で実質的に手当が必要だったのは、load_defaults が 8.1 になったことによる挙動変更と、Spring の同梱廃止、unprocessable_entity の rename、config.action_dispatch.show_exceptions のシンボル化、の 4 つだった。
API-only 構成(--api)でも引っかかる箇所はある。順に書いておく。
load_defaults 8.1
最初の罠。Rails 8.1 で load_defaults をそのまま 8.1 に上げると、新規 framework デフォルトがいくつか有効化される。
# config/application.rb
config.load_defaults 8.1
その上で 以前の挙動を残したい設定だけを new_framework_defaults_8_1.rb で個別に false に戻す のが正解。一気に上げて全部新挙動に乗せると、テストの落ち方の見当がつかなくなる。
私の場合は config.action_dispatch.default_headers の更新と、config.action_controller.allow_deprecated_parameters_hash_equality あたりが効いた。
Spring 廃止
Rails 8 で Spring が同梱から外れた。bin/rails・bin/rake の頭の Spring.binstub を削るか、bin/rails を bundle exec rails 直叩きに切り替えるかどちらか。
私は Spring を消す 側を選んだ。Docker 内で動かす前提だと、Spring の preloader はメリットが薄い。bin/spring を削除し、Gemfile から spring-watcher-listen を抜いた。
show_exceptions のシンボル化
config.action_dispatch.show_exceptions = false だったのが、= :none / :rescuable / :all の 3 値シンボルに変わった。false のままだと dev で ActionView::ActionRescuable が動かない。
# 旧:
config.action_dispatch.show_exceptions = false
# 新:
config.action_dispatch.show_exceptions = :rescuable # = 旧 false 相当
:none だと例外が一切 rescue されないため、開発中に困る。:rescuable が現実的。
unprocessable_entity → unprocessable_content
HTTP 422 のシンボルが :unprocessable_entity から :unprocessable_content に rename された。互換 alias は残っているので慌てる必要はない が、新コードは :unprocessable_content で書くのが正解。
私は controller を全文 grep で書き換えた:
grep -rln 'unprocessable_entity' app/controllers/ | xargs sed -i '' 's/unprocessable_entity/unprocessable_content/g'
まとめ
API-only な Rails アプリの場合、Rails 8.1 の移行コストは 半日 で終わる。Web アプリ(views ありの monolith)はもっと時間がかかると思う。
移行は「どうせいつかやる」を後回しにしないのが近道。半年放置するごとに罠が増える。