Rails.app.dotenvs が明示的なパスを正しく尊重するよう修正
Rails.application の dotenvs アクセサは、デフォルト以外の .env ファイルを指定できるように path 引数を受け取りますが、従来はインスタンスが一度だけ @dotenvs ||= で生成されていたため、最初の呼び出しで渡したパスしか保持されませんでした。結果として、開発環境で Rails.app.creds が先に参照された後に別のパスを指定すると、期待した設定が取得できずにデフォルトの内容が返ってきました。
背景
Rails.application#dotenvs は path 引数で任意の .env ファイルを読み取れる設計でしたが、実装は @dotenvs ||= ActiveSupport::DotEnvConfiguration.new(path) という形でインスタンスを単一化していました。この単一化により、最初に呼び出されたパスがメモ化キーとなり、以降の呼び出しで異なるパスが渡されても無視される挙動が発生しました。
開発モードでは Rails.app.creds が内部で dotenvs(デフォルトパス)を呼び出すため、最初に creds が評価されるとデフォルトの .env が固定化されます。その後、Rails.app.dotenvs("/path/to/custom.env") を実行しても、デフォルトの .env 設定が返される というサイレントなバグが起きました。テストケースでも同様に、デフォルト取得後にカスタムパスを指定すると期待通りのキーが取得できないことが確認されています。
技術的な変更
dotenvs の実装を パス単位でのメモ化 に変更し、@dotenvs をハッシュにして path をキーに保持するようにしました。新しい実装は次の通りです。
def dotenvs(path = Rails.root.join(".env"))
(@dotenvs ||= {})[path] ||= ActiveSupport::DotEnvConfiguration.new(path)
end
変更前は @dotenvs ||= ActiveSupport::DotEnvConfiguration.new(path) という単一インスタンスの生成でしたが、変更後は @dotenvs ||= {} でハッシュを初期化し、@dotenvs[path] ||= ... でパスごとにインスタンスをキャッシュします。この差分により、同一パスの再呼び出しは同一オブジェクトを再利用しつつ、異なるパスは別個のオブジェクトが生成されます。
@dotenvs はこのメソッド以外で参照されていないため、ハッシュへ置き換えても既存コードへの影響はありません。テスト dotenvs honors an explicit path even after the default has been accessed が追加され、デフォルト取得後でもカスタムパスが正しく反映されることを自動的に検証しています。
設計判断
この修正は 「パスを受け取りつつメモ化する」 というアクセサの一貫性を保つ選択です。encrypted(path, …) は毎回新規インスタンスを返す一方、credentials はメモ化のみでパス概念がなく、dotenvs は両方の要素を持つハイブリッドでした。テーブルで示されたように、dotenvs の memoization key を path にする ことは、既存パターンと整合しつつ期待動作を実現する最小変更です。
このアプローチは後方互換性を保持します。Rails.app.dotenvs を引数なしで呼び出す既存コードはハッシュのデフォルトキー Rails.root.join('.env') を利用し、従来通りのインスタンスが取得されます。新たにパスを指定するケースだけがハッシュに別エントリを作成するため、パフォーマンスへの影響は限定的です。
まとめ
Rails.application#dotenvs のメモ化ロジックをパスキー付きハッシュへ変更したことで、明示的な .env パスが正しく尊重されるようになりました。これにより開発環境での Rails.app.creds との相互作用も安全になり、アクセサの設計意図に沿った一貫した動作が保証されます。