こんにちは、ぽっけです。マネーフォワード クラウド会計Plusでエンジニアをしています。
Rails 6.0から、新しいAutoloaderとしてZeitwerkが導入されました。Rails 7.0からは旧来のAutoloader (Classicと呼びます)は使えなくなり、Zeitwerkが必須となりました。
Railsでソースコードを読み込むメソッドにrequire_dependency
があります。require_dependency
は、Zeitwerkでは使う必要がなくなりました。 この記事では、require_dependency
がなぜZeitwerkで必要ないのかを深堀りしようと思います。
require_dependencyとは
そもそもrequire_dependency
とは何でしょうか? このメソッドは、Rubyデフォルトのrequire
やload
の代わりにRailsが提供しているメソッドです。
Autoloading と Reloading
まず、前提としてAutoloadingとReloadingについて簡単に説明します。
Railsは開発体験を向上させるため、定数のAutoloadingとReloadingを提供しています。
Autoloadingは、require
を必要なくする仕組みです。通常のRubyのプログラムではファイルを読み込むためにrequire
メソッドを呼び出しますが、Railsではこれを使いません。その代わり、Railsでは定数名(クラス名)とファイル名を対応させ、定数へのアクセス時に自動でファイルを読み込むようにしています。
Reloadingは、定数を再読込する仕組みです。通常のRubyプログラムでは、一度読み込んだ定数はそのプロセスを再起動しない限り変化しません。つまり、ファイルを編集するたびにプロセスを再起動する必要があります。一方Railsではリクエストのたびに定数をすべて読み込み直す仕組みを提供しています。これによってbin/rails server
の再起動の必要なく開発を進めることができます。
Classic Loaderにおけるrequire_dependency
前述のAutoloadingによって、ほとんどのケースでは明示的なrequire
は必要なくなりました。しかし、Classic Loaderではいくつかのケースにおいて明示的にファイルを読み込む必要がありました。
そのためにRailsはrequire_dependency
というメソッドを提供しています。このメソッドは通常のrequire
やload
を使ってファイルを読み込むとともに、読み込んだファイルが適切にAutoloaderとReloaderに管理されるようにします。Autoloaderの管理下にあるファイルを読み込む場合は、require
やload
ではなく、require_dependency
を使う必要がありました。
通常のケースでは定数はAutoloadingされるため、require_dependency
による明示的な読み込みは不要です。ですが、いくつかのケースで明示的な読み込みが必要でした。 Classic LoaderのRails Guideではrequire_dependency
が必要なケースが具体的に2つ挙げられています。 https://railsguides.jp/autoloading_and_reloading_constants_classic_mode.html#require-dependency
Zeitwerkにおけるrequire_dependency
一方Zeitwerkではrequire_dependency
はobsoleteとなっています。 これはRails Guideや、require_dependency
メソッドのAPIドキュメントから確認できます。
Rails Guideの例
Zeitwerkによって、
require_dependency
の既知のユースケースはすべて削除されました。プロジェクトをgrepしてrequire_dependency
をすべて削除してください。https://railsguides.jp/classic_to_zeitwerk_howto.html#require-dependency呼び出しの削除
APIドキュメントの例
Warning: This method is obsolete. The semantics of the autoloader match Ruby's and you do not need to be defensive with load order anymore. Just refer to classes and modules normally.
Engines that do not control the mode in which their parent application runs should call
require_dependency
where needed in case the runtime mode is:classic
.
このドキュメントの通り、require_dependency
が必要なのは、ZeitwerkとClassicのどちらが使われるかわからないRails Engineを開発している場合のみとなります。通常のRailsアプリケーションで使う必要はないでしょう。
どう置き換えたらよいのか
では、Zeitwerkではrequire_dependency
はどう置き換えていくと良いのでしょうか。Rails Guideで紹介されている方法を解説します。
STIを使っている場合
STIを使っている場合は、Rails Guideにあるコードをrequire_dependency
の代わりに使用すると良いでしょう。 https://railsguides.jp/autoloading_and_reloading_constants.html#sti(単一テーブル継承)
このコードを使用すると、DBからクラス名を読み取り、対応するクラスを自動的に読み込むようになります。これによって明示的なrequire_dependency
の必要なく、必要なクラスを読み込めます。
(個人的にはこのコードはworkaround感が強く、使うことに少々抵抗があります。とはいえ良い代替方法もないため、これを使うのが良いでしょう。StiPreload
相当のものをActive Recordが提供してほしい気もする…。)
ちなみにSTIとAutoloadingの相性が悪い問題は、Classic Loaderのドキュメントで詳しく解説されています。 https://railsguides.jp/autoloading_and_reloading_constants_classic_mode.html#自動読み込みとsti Zeitwerkでも問題の原因は同じであるため、興味がある場合は読んでみると良いでしょう。
Classic Loaderで適切にAutoloadingできなかった定数の場合
これは、Rails Guideの次のページに説明されています。 https://railsguides.jp/constant_autoloading_and_reloading.html#定数がトリガーされない場合
これは異なる名前空間に同名のクラスがある時に問題となります。次の例を考えてみましょう。
class C end
module N class C end end
module N class D p C # => ??? end end
この例のn/d.rb
で定数C
を参照するときの挙動が問題になります。Classic Loaderでは、c.rb
とn/c.rb
の読み込み状況によって、n/d.rb
で参照されるC
が異なったものを指してしまっていました。そのため、Classic Loaderではn/d.rb
内でC
を評価する前に確実にn/c.rb
を読み込む必要があり、そのためにrequire_dependency
が必要になっていました。
一方、Zeitwerkの場合はこのrequire_dependency
は全く必要ありません。Zeitwerk
はRubyのautoload
メソッドを使って予めAutoloading対象の定数をすべて定義するため、読み込まれているファイルによる曖昧性が起きません。そのため、このようなケースでrequire_dependency
を使っていた場合には、これを単に削除できます。
その他の場合
この他の理由でrequire_dependency
を使っているケースもあるでしょう。その場合の方針は示されていませんが、単にrequire_dependency
を削除するか、もしくは定数を参照するコードでrequire_dependency
を置き換えると良いでしょう。
まず、require_dependency
で読み込みたい定数がコード中で参照されているような場合は、require_dependency
の呼び出しは削除できるでしょう。「Classic Loaderで適切にAutoloadingできなかった定数の場合」と同様の理由です。Rubyのautoload
が使われていることで定数解決の曖昧性が起こらず、Rubyの定数解決と全く同じようにAutoloadingがされるため、require_dependency
は必要ありません。
一方、STIのように定数が読み込まれていることに意味がある場合には、単にrequire_dependency
を削除するだけでは不十分です。たとえばClass#descentants
メソッドでクラスを列挙するようなケースが該当するでしょう。そのような場合は、require_dependency
の代わりに定数を参照することで定数を読み込むと良いでしょう。STIの例のようにDBなどから定数名を読み込んでも良いでしょうし、コード中に定数名を書いても良いでしょう。
どちらの場合でもrequire_dependency
をrequire
で置き換えてはいけません。Autoloading対象のファイルをrequire
で読み込んではいけないことは、Rails Guideでも明記されています。 https://railsguides.jp/classic_to_zeitwerk_howto.html#不要なrequire呼び出しはすべて削除すること
いかがでしたでしょうか? この記事ではZeitwerkにおけるrequire_dependency
の立ち位置についてまとめました。基本的にはRails Guideなどにある内容の再構成ですが、require_dependency
に対処していく際にはまとまった情報として役立つかもしれません。
なお、Classic LoaderをZeitwerkに置き換えていく際にはrequire_dependency以外の対応も必要となるでしょう。下記でリンクしているRails Guideや、他社の事例を読むとその手助けになるでしょう。
参考リンク
Rails Guideは、主に次の3つの記事が関連しています。
- 定数の自動読み込みと再読み込み (Zeitwerk) - Railsガイド
- 定数の自動読み込みと再読み込み (Classic) - Railsガイド
- Classic Loaderの課題や
require_dependency
のユースケースが説明されています。Zeitwerkに直接関係はしていませんが、移行時に読む価値はあるでしょう。
- Classic Loaderの課題や
- Classic から Zeitwerk への移行 - Railsガイド
次のQiitaの記事には、Zeitwerkの挙動で問題となりやすい箇所がまとまっていて一読の価値があります。
require_dependency
にはあまり関係ありませんが、実際のアップデートの記録はZeitwerkへの切り替え作業時に役に立つでしょう。
今回解説した対象のコードは、次のリンクから主に見れます。
- https://github.com/fxn/zeitwerk
- https://github.com/rails/rails/blob/v7.0.3/activesupport/lib/active_support/dependencies/require_dependency.rb Rails 7における
require_dependency
の実装 - https://github.com/rails/rails/blob/v6.1.6/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb#L45-L54 Rails 6系ではソースコードの場所が少し違います。
- https://github.com/rails/rails/blob/v6.1.6/activesupport/lib/active_support/dependencies.rb#L282-L289 Classic Loaderでの
require_relative
の実装。Rails 7では削除されました。
マネーフォワードでは、エンジニアを募集しています。 ご応募お待ちしています。
【会社情報】 ■Wantedly ■株式会社マネーフォワード ■福岡開発拠点 ■関西開発拠点(大阪/京都)
【SNS】 ■マネーフォワード公式note ■Twitter - 【公式】マネーフォワード ■Twitter - Money Forward Developers ■connpass - マネーフォワード ■YouTube - Money Forward Developers