Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: configuration あるいは load_defaults の話

こんにちは ebi です。
非常に今更ながら初めてまともな Rails の記事書く気がします。興奮してきた......!?

さて、先日無事に Rails 7.2 がリリース されましたので Rails で構築されたWebアプリケーションを継続的に開発・運用している方々におきましては、そう遠くないうちに Rails 7.2 へのアップグレード対応を検討することになるのではないかと存じます。

今回はそんな Rails アップグレード対応の一環でも関わる Rails の config 制御、及び load_defaults の設定値の更新に関する知見を少しまとめてみます。
何を隠そう私自身が直近で Rails のアップグレード対応(Rails 7.2 へのアップグレード対応とは言っていない)をした際の内容を元に取り留めなくまとめる機会を作りましたが、人には人の Rails アップグレード対応が必要です。完全性がない点は予めご了承ください。

また、前提となる Rails アップグレードの基本的な流れの説明などは省きますのでそうした基礎は公式ドキュメントを、あるいは信頼のおける参考文献を元にしてください。

ザックリ言うと load_defaults の設定値を更新することで Rails 7.2 の内部的な新機能(少なくとも実装者が意図的に新しいメソッド、記述をしない限り使えない機能とかではないもの)がまとめて有効化されるあたりの仕組みを先に押さえておいてください。

早めの結論( TL;DR )

とりあえず Rails アップグレードの作業に関わる人はこの辺の知識を覚えて帰ってください、と言う趣旨の記事です。

  • 俺の Rails アプリの load_defaults の設定値は config.load_defaults 6.1 のまま時が止まっているが、この設定値のおかげで Rails 6.1 時点基準の古い仕様で動く互換性が担保されているから、とりあえず気にせず Rails 7.2 にアップグレードしてしまうぜ!?
    ⇒ load_defaults の役割の認識が間違ってます
    load_defaults の定義は Rails バージョンリリースごとに各 load_defaults の過去のバージョンを遡って内容が見直し・更新され、古い仕様の互換性に対応する config は随時削除されているので必ずバージョンごとの変更差分を確認しましょう。
    また、 load_defaults を介することなくデフォルト値がセットされたり、削除される config もあります。
    詳しくはこちら

  • よーし、早速 Rails 7.2 にアップグレード作業するぞ。面倒だし config.load_defaults 7.2 の上書きまで一遍にまとめて動作確認、リリースしちゃうぜ
    ⇒ 判断が早い
    ものによっては切り戻しが不可能な config 設定に更新されてしまう場合もありますので、必要に応じて Rails 本体のアップグレードと分けてリリースする段階を踏むことを検討しましょう。
    アップグレードガイドやリリースノートでも言及されるだろうし具体的な注意点は直に世の中で発信されていくはず......と言うことで詳しい説明は割愛

  • ターボ知ってるもん!( Rails の Turbo と韻を踏んだ高度なギャグ)
    config/initializers/new_framework_defaults_7_2.rb のファイル中のコメントアウトを一つずつ外してテスト実行や動作確認が問題なければ config.load_defaults 7.2 に更新して良いんだもん!!
    ⇒ 基本的にはそれで良いんだけど......
    実は Rails アプリの状況によっては initializers のタイミングでの config 設定が効かずに load_defaults を上げるための事前検証ができてないケースがあり得ます。
    詳しくはこちら

  • bin/rails app:update を実行したら追加されていく config/initializers/new_framework_defaults_7_2.rb などのファイル
    勝手に消しちゃマズいだろうからそのまま放置しておこう。
    ⇒ うーん、困りはしないんだけど......
    load_defaults の設定値を更新し、役目を終えた new_framework_defaults_x_x.rb のファイルは削除しましょう
    (と言うかドキュメントでそう案内されているぞ!)

configuration 及び load_defaults の config 定義の変遷を追いかける

まず configuration の定義元を教えちゃいましょう。 railties/lib/rails/application/configuration.rb です。困ったらソースコード読んで仕組みを理解しちゃうのが良い。

load_defaults 設定値更新などによって、新機能やおススメ設定のON/OFFを切り替えることができる可能性がある config 達は次のような変遷を辿ります。

新規追加 ⇒ ( load_defaults でセットされる値の更新 ⇒ )deprecated 扱い ⇒ config での制御機能の削除 ⇒ config の存在ごと完全削除

各段階は適時まとめて飛ばされたりしますのでご注意ください。

せっかくなので直近の Rails 7.2 に近い config から上記のような変遷の具体例を見繕ってみましょう。

新規追加された config

例えば Rails 7.2 では active_job.enqueue_after_transaction_commit と言う config が追加されています。

config.load_defaults 7.2 を適用することでおススメ設定の :default がセットされますが、 config.load_defaults 7.1 以下が適用されている場合はデフォルト値として :never がセットされるように実装されています
多分、これで過去のバージョンでの仕様にあわせて動くような後方互換性を取ってくれているはずです。

load_defaults でセットされる値が更新される config

単純にON/OFFを切り替えるだけではないタイプの config の場合、 load_defaults を介してセットされる config の値が何回か更新されていく場合があります。

このケースは 7.2 のバージョン内での更新内にはなさそうなので、例えば 7.1 で更新が入っている config.action_dispatch.default_headers の定義を確認してみます。
Rails ガイドの「Configuring Rails Applications」ドキュメント内には該当の config が load_defaults の設定値ごとにどの値がセットされるか表になっているので、この表を見ると分かりやすいですね。

6.1以前 ⇒ 7.0 ⇒ 7.1以降 と段階を踏んで load_defaults としてセットされる値が更新されていることが分かります。

そのため、例えば今から EOL の発表にあわせて Rails 6.1 ⇒ 7.0 に慌てて上げようとしている人たちの場合、余裕があれば(余裕はないか。例が良くないね)後続の最新バージョンで 7.0 時点で新たに追加される config がどのような変遷を辿るのかを確認してみても良いと思います。
config.action_dispatch.default_headers のケースにおいて、先んじた設定の取入れが本当に有効かまではここでは確認してませんが、それを踏まえた上で Rails 7.0 へのアップデート時点から config.load_defaults 7.0 の設定によるデフォルト設定値を受け入れるのではなく、独自に load_defaults 7.1 時点でのデフォルト設定値に先回りして上書きする方が良い場合もありそうです。

deprecated 扱いされた config

これも Rails 7.1 ⇒ 7.2 の差分だと丁度良い例がない気がするな......。

なんなら config が新規追加されたと同時に deprecated 扱いされている config の例もありますね。
例えば、このあと紹介する active_record.commit_transaction_on_non_local_return がそのケースに該当します

機能制御の役割を終えた config

load_defaults での定義がある config

Rails 7.1 ⇒ 7.2 の差分において load_defaults 7.1 として定義されていた config.active_record.commit_transaction_on_non_local_return = true の定義が削除されました。

その load_defaults 側の定義削除と同時に実装側では config.active_record.commit_transaction_on_non_local_return の値を元に分岐する処理機能が削除されています。

load_defaults を介さずに直接独自に config としてセットしようとしている、あるいはなんらかの処理で直接参照しようとした場合には警告メッセージを出力してあげてそうです。
(こう言う既に動かなくなってるのか、このあと動かなくなるのかの deprecated 扱いの差ってどう見分ければ良いんですかね?都度ちゃんと内容確認するしかないんか?)

load_defaults と関係ない config

頭がこんがらがってきますが load_defaults を介さない config の話も軽く触れておきます。
こちらは railties/lib/rails/application/configuration.rb の差分だけで話が完結していて分かりやすいケース。

config.read_encrypted_secrets については initialize のセット時のデフォルト値の定義がなくなり config.read_encrypted_secrets の値を参照、セットする時に警告メッセージが出力されるようになっているはずです。


完全削除された config

これも例は load_defaults を介さない config の話です。

一方で config.enable_dependency_loading は、先ほどの config.read_encrypted_secrets と同様に initialize のセット時のデフォルト値の定義がなくなっていたのですが、

こちらは事前に仕込んでいた deprecated 扱いの警告メッセージ出力も同時に消されてるので、これにてconfigの存在ごと完全削除のパターンです。

Rails アップグレードを機に完全に振舞が変わる機能を見逃すな

と言うことで、 load_defaults の設定値を更新するのを後回しにするからと言って、 configuration 周りの差分を確認せずに Rails アップグレード対応を行おうとするのは止めましょう。
リリースノートには記載があるはずですが、一番良いのは deprecated 扱いされた実装の利用があれば出力されるであろうDEPRECATE WARNINGのメッセージを日々検知して Rails アップグレード前に早めに潰しておくことだとは思います。

ところで、僕は GitHub の compare 機能 でバージョン間のソースコード差分 を表示して、その中から railties/lib/rails/application/configuration.rb をどうにか見つけ出して configuration の差分を直接確認したりしたんですけど、これファイル単位で compare ってできたりしないんですかね? 🤔

new_framework_defaults_X_Y.rb のほんとにあった怖い話

本来想定されているであろう new_framework_defaults_X_Y.rb の運用方法

Upgrading Ruby on Rails - 1.5 Configure Framework Defaults」の案内通りですが、
bundle updaterails app:update の実行の後、 config.load_defaults の値を一気に更新するのではなく、
config/initializers/new_framework_defaults_X_Y.rb のファイル内に記載されたコメントアウトを利用して1つずつ、あるいは影響度の大きそうなもの以外を取り急ぎまとめて有効化することが可能です。

このファイル内の全ての設定が有効化できた後(もしくは一部の設定値はプロジェクト方針として意図的に load_defaults で設定される値と違う値を一時的に、あるいは恒常的に設定することが決まり application.rb に個別に定義した後)、
config.load_defaults の値を更新するのにあわせてこのファイルは削除して良いものになります。

なお load_defaults の設定値更新のみを放置したりすると、上述の通り最早使用されていないものや後続のバージョンで上書きされる config への記載がファイル中のコメントアウトに含まれたりするので、その Rails バージョンで動くそのバージョンに対応する load_defaults 定義に即したものでは無い可能性も高いです。
(例えば config.load_defaults 7.0 の設定値のまま Rails 7.2 まで上げてしまった Rails 環境における new_framework_defaults_7_1.rb ファイル内の active_record.commit_transaction_on_non_local_return に対する config 指定のコメントアウトを外したところで振舞は変わらず、何の意味もありません)
もしもやむを得ずに時間を置いてまとめて Rails 本体、もしくは load_defaults の設定値のみのバージョンを上げることになる際にはご注意ください。

new_framework_defaults_X_Y.rb のファイル置き場が抱える問題点

config/initializers/new_framework_defaults_7_2.rb って置き場にある以上、このファイルは initializers 以下を読み込む段階、つまり Rails 本体と gem の読み込みが終わった後に 他の initializers 以下のファイル同様に順に読み込まれます。
そのため config/application.rb 内で config.load_defaults 7.2 を設定した場合、あるいは直接同内容の config 設定をした場合、と new_framework_defaults_7_2.rb 内のコメントアウトを外して新しい機能を有効化しようとした場合で挙動が違うケースがあり得るようです。
(参考: belongs_to_required_by_default を設定しているのに効かない場合の対応

そんなこと言い出したら new_framework_defaults_X_Y.rb が自動生成される意味とは?って感じなんですが、実際そう言う罠を踏んだ身からすると一応ね......。
なるべく Rails 本体のみで開発できている場合は避けられる問題かもしれないし、流石に有名どころの gem の最新バージョンとかで今もこの状態と言うことはあまりなさそう。滅茶苦茶今更感ありますが、せっかくなのでこの場で改めて注意喚起しておきます。
まぁこう言う Rails の仕組みを理解しておくのは大事だし gem 以外の要因でも遭遇するかもなので頭の片隅に入れておいてあげてください。

おわり

本当は実際に Rails 7.2 環境で動かした動作を色々見たかったけどそんな余裕は無かったよ......。

その他にも Rails アップグレードの作業では bin/rails app:update を実行した後の差分検証が地獄みたいなあるあるネタ(?)もありますが今日取り上げてみた話としてはここまでです。



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。