背景
Rails ではファイルを require
しなくても自然と動作するので、不思議にも感じなかったのですが、同じ名前のファイルが有る時の挙動がよくわからなかったので Rails の自動読み込みについて調べました。まとまった情報は http://railsguides.jp/constant_autoloading_and_reloading.html にあるのですが、非常に読みにくく感じたので自分なりにまとめました。
Rails の自動読み込み
Rails は require を使わなくても、ファイルを読み込みます。たとえば Rails がプログラム実行中に未定義の定数 X
と遭遇した時、どのように振る舞うのかを下に書きます。
- Rails は定数と同じ名前のファイルを探す。定数
X
ならx.rb
を探す。 - 全ての
config.autoload_paths
パスを順に調べる。 - たとえば
.../assets/x.rb
を調べる。なければ.../controller/x.rb
を調べる。同様に全てのパスを調べる。 - 探索の結果、ファイルが見つかれば、そのファイルを読み込み、プログラムを再開する。
- 見つからなければ通常の
const_missing
と同じエラーを返す。
自動読み込みの優先度
定数 X
を読んだときに x.rb の探索が行われるのですが models/x.rb
と models/concerns/x.rb
両方が存在する場合、どちらが読み込まれるのでしょうか。特に設定がなければ models/x.rb
が読まれます。なぜなら、autoload_paths
で models/
が先に登場するからです。(ActiveSupport::Dependencies.autoload_paths
を実行すると確認出来ますが、ドキュメントに記載がないので、これは意図したものではないかもしれません)
明示的に models/concern/x.rb
を読み込みたいならば、定数 X
が登場するより前に require_dependency "concerns/x.rb"
を実行しなくてはいけません。単なる require
は Rails の自動読み込みと違って、development モードでのホットリロードや、production モードでのプリロードを行わないので、使うべきではありません。
Ruby の定数解決
Rails は参照された定数が未定義であれば上記のような自動読み込みを行います。しかし、Ruby の特殊な名前解決によって、未定義に見える定数が、定義済みとみなされる事があります。
たとえば、String::Array
を参照してみてください。すると warning: toplevel constant String referenced by Array::String
と表示されます。未定義の定数 String::Array
を参照したつもりが定義済みのトップレベル定数 Array
を参照してしまうということです。
Rails では、同じ名前のファイルを定義した場合、これが問題になりやすいです。読まれる順序によっては、正しく動作することもあるので、見つけにくいバグになります。確実に読み込ませるために require_dependency
を使う手もありますが、別々のクラス名にするのが簡単でわかりやすいので、こちらのほうが良いと思います。