共有セクションが欠如する接続でもデータベース設定がロードできるように
Rails の 3 層 database.yml において、shared セクションに定義されていない接続が nil になると reverse_merge! が呼び出され NoMethodError が発生していました。この PR はその例外ケースを防ぎ、欠如した接続はそのまま残すことで安全に設定を取得できるようにしています。
背景
問題の根源は shared セクションと個別接続のマッピングが不完全なときに起こる例外 です。database.yml に shared に one だけを定義し、development に two を記述すると、内部で shared["two"] が nil となり nil.reverse_merge! が実行されていたため、Rails の起動時に NoMethodError がスローされました。この挙動は 3‑tier 設定を採用したプロジェクトで潜在的に障害を引き起こすリスクがありました。
修正の目的は例外を回避しつつ、既存のマージロジックはそのまま保持すること です。shared に対応するサブセクションが無い場合はマージ処理をスキップし、該当接続は元の設定のまま利用できるようにします。これにより、開発者は shared を部分的にだけ利用でき、設定ファイルの柔軟性が向上します。
技術的な変更
railties/lib/rails/application/configuration.rb の database_configuration メソッドに条件分岐が追加されました。具体的には sub_config.reverse_merge!(shared[name]) を sub_config.reverse_merge!(shared[name]) if shared[name] に変更し、shared[name] が nil のときはマージを実行しません。
@@ -493,7 +493,7 @@ def database_configuration
if config.is_a?(Hash) && config.values.all?(Hash)
if shared.is_a?(Hash) && shared.values.all?(Hash)
config.map do |name, sub_config|
- sub_config.reverse_merge!(shared[name])
+ sub_config.reverse_merge!(shared[name]) if shared[name]
end
else
この変更は 1 行の追加で済み、既存の `shared` マッピングが存在するケースでは従来通り `reverse_merge!` が適用されます。`shared` が無い接続はそのまま保持され、例外は回避されます。さらに、テストスイートに新しいケースが追加され、`shared` から欠如した接続が正しくロードされることが自動的に検証されます。
```ruby:railties/test/application/configuration_test.rb
+ test "loads 3-tier database.yml when a connection is absent from the shared subsections" do
+ app_file "config/database.yml", <<-YAML
+ shared:
+ one:
+ migrations_path: "db/one"
+
+ development:
+ one:
+ adapter: sqlite3
+ two:
+ adapter: sqlite3
+ YAML
+
+ app "development"
+
+ ar_config = Rails.configuration.database_configuration
+ assert_equal "db/one", ar_config["development"]["one"]["migrations_path"]
+ assert_equal "sqlite3", ar_config["development"]["two"]["adapter"]
+ end
テストは `shared` に `one` だけが定義され、`two` が無い状態でも `development` の設定が正しく取得できることを確認しています。これにより、回帰が防止され、将来的な変更でも安全性が保たれます。
## 設計判断
**防御的プログラミングによる安全性の確保** が今回の主要な設計判断です。`shared[name]` が `nil` のときに `reverse_merge!` を呼び出すリスクを事前に排除することで、設定読み込み時の例外を根本的に防ぎました。コード変更は一行の条件追加に留め、既存ロジックへの侵入性を最小限に抑えているため、後方互換性が維持されます。
**変更はロジックの可視性を保ちつつ、API の振る舞いを変えない** アプローチです。`shared` キー自体の構造や形式は変更せず、単にマージ対象が存在するかをチェックするだけなので、利用者側の設定ファイルを書き換える必要はありません。この設計は Rails が提供する「設定は宣言的であるべき」という理念に沿っています。
## まとめ
`shared` セクションにマッピングされていない接続が `nil` になるバグを、`shared[name]` が存在する場合に限定して `reverse_merge!` を実行する条件分岐で解消しました。追加されたテストにより、3‑tier `database.yml` が欠如した接続を含んでも例外なくロードできることが保証されます。この修正は例外安全性の向上と設定柔軟性の拡大を同時に実現し、Rails アプリケーションのデータベース設定管理をより堅牢にします。