edge
詳細はrubyonrails.orgで: もっとRuby on Rails

自動読み込みと定数の再読み込み

このガイドでは、zeitwerkモードでの自動読み込みと再読み込みの仕組みについて説明します。

このガイドを読み終えると、以下のことがわかります。

1 はじめに

このガイドでは、Railsアプリケーションにおける自動読み込み、再読み込み、イーガーローディングについて説明します。

通常のRubyプログラムでは、使用するクラスやモジュールを定義するファイルを明示的に読み込む必要があります。たとえば、次のコントローラーはApplicationControllerPostを参照しており、通常はこれらのためにrequireを使用します。

# これは行わないでください。
require "application_controller"
require "post"
# これは行わないでください。

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

しかし、Railsアプリケーションでは、アプリケーションのクラスやモジュールはrequireを使用せずにどこでも利用できます。

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Railsは、必要に応じてこれらを自動的にロードします。これは、Railsが自動読み込み、再読み込み、イーガーローディングを提供するために設定されたZeitwerkローダーのおかげです。

一方、これらのローダーは他の何も管理しません。特に、Rubyの標準ライブラリ、gemの依存関係、Railsのコンポーネント自体、そして(デフォルトでは)アプリケーションのlibディレクトリさえも管理しません。そのコードは通常どおりにロードする必要があります。

2 プロジェクトの構造

Railsアプリケーションでは、ファイル名は定義する定数と一致し、ディレクトリは名前空間として機能します。

たとえば、ファイルapp/helpers/users_helper.rbUsersHelperを定義し、ファイルapp/controllers/admin/payments_controller.rbAdmin::PaymentsControllerを定義する必要があります。

デフォルトでは、Railsはファイル名をString#camelizeで変換するようにZeitwerkを設定しています。たとえば、app/controllers/users_controller.rbが定数UsersControllerを定義することを期待しています。なぜなら、"users_controller".camelizeが返す値がそれだからです。

以下の「インフレクションのカスタマイズ」のセクションでは、このデフォルトを上書きする方法について説明します。

詳細については、Zeitwerkのドキュメントを参照してください。

3 config.autoload_paths

自動読み込みおよび(オプションで)再読み込みされるアプリケーションディレクトリのリストを「自動読み込みパス」と呼びます。たとえば、app/modelsなどです。これらのディレクトリはルート名前空間であるObjectを表します。

Zeitwerkのドキュメントでは、自動読み込みパスを「ルートディレクトリ」と呼んでいますが、このガイドでは「自動読み込みパス」と呼びます。

自動読み込みパス内では、ファイル名はここで説明されているように、定義する定数と一致する必要があります。

デフォルトでは、アプリケーションの自動読み込みパスは、アプリケーションが起動するときに存在するappのすべてのサブディレクトリ(assetsjavascriptviewsを除く)と、依存する可能性のあるエンジンの自動読み込みパスで構成されています。

たとえば、UsersHelperapp/helpers/users_helper.rbに実装されている場合、モジュールは自動的に読み込まれますので、requireの呼び出しは必要ありません(また、書いてはいけません)。

$ bin/rails runner 'p UsersHelper'
UsersHelper

Railsは、appの下にカスタムディレクトリを自動的に追加します。たとえば、アプリケーションにapp/presentersがある場合、プレゼンターを自動的に読み込むために何も設定する必要はありません。そのまま使えます。

デフォルトの自動読み込みパスの配列は、config/application.rbまたはconfig/environments/*.rbconfig.autoload_pathsに追加することで拡張できます。たとえば:

module MyApplication
  class Application < Rails::Application
    config.autoload_paths << "#{root}/extras"
  end
end

また、エンジンはエンジンクラスの本体と独自のconfig/environments/*.rbで追加できます。

ActiveSupport::Dependencies.autoload_pathsを変更しないでください。自動読み込みパスを変更する公開インターフェースはconfig.autoload_pathsです。

アプリケーションの起動中に自動読み込みパス内のコードを自動読み込むことはできません。特に、config/initializers/*.rbで直接行うことはできません。有効な方法については、以下の「アプリケーションの起動時の自動読み込み」を参照してください。

自動読み込みパスは、Rails.autoloaders.mainローダーによって管理されます。

4 config.autoload_lib(ignore:)

デフォルトでは、libディレクトリはアプリケーションやエンジンの自動読み込みパスに含まれません。

config.autoload_lib設定メソッドは、libディレクトリをconfig.autoload_pathsおよびconfig.eager_load_pathsに追加します。これはconfig/application.rbまたはconfig/environments/*.rbから呼び出す必要があり、エンジンでは使用できません。

通常、libには自動読み込みローダーで管理すべきではないサブディレクトリが含まれています。そのため、必要な場合は、ignoreキーワード引数にlibに対する相対パスを指定してください。たとえば:

config.autoload_lib(ignore: %w(assets tasks))

なぜ?assetstasksは通常のコードとlibディレクトリを共有していますが、その内容は自動読み込みやイーガーロードの対象ではありません。AssetsTasksはそこではRubyの名前空間ではありません。ジェネレーターも同様です。 ruby config.autoload_lib(ignore: %w(assets tasks generators))

config.autoload_libは7.1より前では利用できませんが、アプリケーションがZeitwerkを使用している限り、それをエミュレートすることはできます。

# config/application.rb
module MyApp
  class Application < Rails::Application
    lib = root.join("lib")

    config.autoload_paths << lib
    config.eager_load_paths << lib

    Rails.autoloaders.main.ignore(
      lib.join("assets"),
      lib.join("tasks"),
      lib.join("generators")
    )

    ...
  end
end

5 config.autoload_once_paths

リロードせずにクラスやモジュールを自動読み込みしたい場合、autoload_once_paths設定にコードを保存できます。

デフォルトでは、このコレクションは空ですが、config.autoload_once_pathsに追加することで拡張することができます。これはconfig/application.rbまたはconfig/environments/*.rbで行うことができます。例えば:

module MyApplication
  class Application < Rails::Application
    config.autoload_once_paths << "#{root}/app/serializers"
  end
end

また、エンジンはエンジンクラスの本体とconfig/environments/*.rbに追加することができます。

app/serializersconfig.autoload_once_pathsに追加されると、Railsはこれをautoloadパスとは見なさなくなりますが、appのカスタムディレクトリであるにもかかわらず、この設定はそのルールを上書きします。

これは、Railsフレームワーク自体のようなリロードを生き残る場所にキャッシュされたクラスやモジュールに対して重要です。

例えば、Active JobのシリアライザはActive Job内に保存されています。

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

そして、Active Job自体はリロードされません。リロードがある場合、アプリケーションとエンジンのコードのみがautoloadパスにある場合にのみリロードされます。

MoneySerializerをリロード可能にすると混乱するため、編集したバージョンをリロードしてもActive Jobに保存されているクラスオブジェクトには影響しません。実際、MoneySerializerがリロード可能であれば、Rails 7以降ではそのような初期化子がNameErrorを発生させます。

もう1つのユースケースは、エンジンがフレームワーククラスをデコレートする場合です。

initializer "decorate ActionController::Base" do
  ActiveSupport.on_load(:action_controller_base) do
    include MyDecoration
  end
end

ここでは、初期化子が実行される時点でMyDecorationに保存されているモジュールオブジェクトがActionController::Baseの祖先になり、MyDecorationをリロードすることは意味がありません。それはその祖先チェーンに影響を与えません。

autoload onceパスのクラスとモジュールはconfig/initializersで自動読み込みされます。したがって、この設定では次のように動作します。

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

技術的には、onceオートローダーで管理されるクラスとモジュールをbootstrap_hookの後に実行される任意の初期化子で自動読み込みすることができます。

autoload onceパスはRails.autoloaders.onceで管理されます。

6 config.autoload_lib_once(ignore:)

config.autoload_lib_onceメソッドはconfig.autoload_libと似ていますが、libconfig.autoload_once_pathsに追加します。これはconfig/application.rbまたはconfig/environments/*.rbから呼び出す必要があり、エンジンでは利用できません。

config.autoload_lib_onceは7.1より前では利用できませんが、アプリケーションがZeitwerkを使用している限り、それをエミュレートすることはできます。

# config/application.rb
module MyApp
  class Application < Rails::Application
    lib = root.join("lib")

    config.autoload_once_paths << lib
    config.eager_load_paths << lib

    Rails.autoloaders.once.ignore(
      lib.join("assets"),
      lib.join("tasks"),
      lib.join("generators")
    )

    ...
  end
end

7 $LOAD_PATH

デフォルトでは、autoloadパスは$LOAD_PATHに追加されます。ただし、Zeitwerkは内部的に絶対ファイル名を使用しており、アプリケーションはautoload可能なファイルのためにrequire呼び出しを発行すべきではないため、これらのディレクトリは実際にはそこに必要ありません。このフラグを使用してオプトアウトすることができます。

config.add_autoload_paths_to_load_path = false

これにより、正当なrequire呼び出しの数が少なくなるため、少し速くなる場合があります。また、アプリケーションがBootsnapを使用している場合、ライブラリが不要なインデックスを構築することを防ぐため、メモリ使用量が低下します。

このフラグはlibディレクトリには影響しません。libディレクトリは常に$LOAD_PATHに追加されます。

8 リロード

Railsは、autoloadパスのアプリケーションファイルが変更された場合に自動的にクラスとモジュールをリロードします。

具体的には、ウェブサーバーが実行中であり、アプリケーションファイルが変更された場合、Railsは次のリクエストが処理される直前に、mainオートローダーによって管理されるすべてのautoloadされた定数をアンロードします。これにより、そのリクエスト中に使用されるアプリケーションのクラスやモジュールが再度autoloadされ、ファイルシステムの現在の実装を取得します。

リロードは有効または無効にすることができます。この動作を制御する設定はconfig.enable_reloadingです。これは、developmentモードではデフォルトでtrueproductionモードではデフォルトでfalseです。互換性のために、Railsはconfig.cache_classesもサポートしており、これは!config.enable_reloadingと同等です。

Railsはデフォルトでイベント駆動型のファイルモニターを使用してファイルの変更を検出します。代わりにautoloadパスを歩いてファイルの変更を検出するように設定することもできます。これはconfig.file_watcher設定で制御されます。

Railsコンソールでは、config.enable_reloadingの値に関係なく、ファイルウォッチャーはアクティブになりません。これは、通常、コンソールセッションの中でコードがリロードされると混乱するためです。個々のリクエストと同様に、一貫性のある、変更されないアプリケーションのクラスとモジュールによってコンソールセッションが提供されることが一般的です。 ただし、コンソールでreload!を実行することで強制的にリロードすることができます。

irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Reloading...
=> true
irb(main):003:0> User.object_id
=> 70136284426020

ご覧の通り、リロード後にUser定数に格納されているクラスオブジェクトが異なります。

8.1 リロードと古いオブジェクト

Rubyには、クラスやモジュールをメモリ上で本当にリロードし、それが既に使用されているすべての場所に反映される方法はありません。技術的には、「アンロード」することは、Object.send(:remove_const, "User")を使用してUser定数を削除することを意味します。

例えば、次のRailsコンソールセッションをご覧ください。

irb> joe = User.new
irb> reload!
irb> alice = User.new
irb> joe.class == alice.class
=> false

joeは元のUserクラスのインスタンスです。リロードがある場合、User定数は異なるリロードされたクラスに評価されます。aliceは新しくロードされたUserのインスタンスですが、joeはそうではありません - 彼のクラスは古くなっています。joeを再定義したり、IRBのサブセッションを開始したり、reload!を呼び出す代わりに、新しいコンソールを起動することができます。

また、リロードされない場所でリロード可能なクラスをサブクラス化する場合にも、この注意点に遭遇することがあります。

# lib/vip_user.rb
class VipUser < User
end

Userがリロードされる場合、VipUserはリロードされないため、VipUserのスーパークラスは元の古いクラスオブジェクトです。

要点:リロード可能なクラスやモジュールをキャッシュしないでください

9 アプリケーションの起動時の自動読み込み

起動時、アプリケーションはautoload_once_pathsから自動的に読み込むことができます。これはonceオートローダーによって管理されます。詳細については、上記のconfig.autoload_once_pathsセクションをご覧ください。

ただし、config/initializersやアプリケーションやエンジンの初期化子でのコードなど、mainオートローダーによって管理されるオートロードパスからは自動的に読み込むことはできません。

なぜなら、初期化子はアプリケーションの起動時にのみ実行されるためです。リロード時には再度実行されません。初期化子がリロード可能なクラスやモジュールを使用している場合、それらの編集は初期コードに反映されず、古くなってしまいます。したがって、初期化中にリロード可能な定数を参照することは許可されていません。

代わりに、どうすればよいかを見てみましょう。

9.1 ユースケース1:起動時にリロード可能なコードを読み込む

9.1.1 起動時とリロード時の両方で自動読み込み

ApiGatewayがリロード可能なクラスであり、アプリケーションの起動時にエンドポイントを設定する必要があると想像してみましょう。

# config/initializers/api_gateway_setup.rb
ApiGateway.endpoint = "https://example.com" # NameError

初期化子はリロード可能な定数を参照することはできませんので、to_prepareブロックでそれをラップする必要があります。このブロックは起動時とリロード時の後に実行されます。

# config/initializers/api_gateway_setup.rb
Rails.application.config.to_prepare do
  ApiGateway.endpoint = "https://example.com" # CORRECT
end

注意:歴史的な理由から、このコールバックは2回実行される場合があります。実行するコードは冪等である必要があります。

9.1.2 起動時のみ自動読み込み

リロード可能なクラスやモジュールは、after_initializeブロックでも自動的に読み込むことができます。これらは起動時に実行されますが、リロード時には再度実行されません。特殊なケースでは、これが必要な場合もあります。

プリフライトチェックはこのようなユースケースです。

# config/initializers/check_admin_presence.rb
Rails.application.config.after_initialize do
  unless Role.where(name: "admin").exists?
    abort "The admin role is not present, please seed the database."
  end
end

9.2 ユースケース2:起動時にキャッシュされたコードを読み込む

一部の設定では、クラスやモジュールオブジェクトを受け入れ、それをリロードされない場所に格納します。これらがリロード可能であってはならないことが重要です。なぜなら、編集がそれらのキャッシュされた古いオブジェクトに反映されないからです。

ミドルウェアがその例です。

config.middleware.use MyApp::Middleware::Foo

リロード時にはミドルウェアスタックは影響を受けないため、MyApp::Middleware::Fooがリロード可能であることは混乱を招くでしょう。その実装の変更は効果を持ちません。

もう1つの例はActive Jobのシリアライザです。

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

初期化時にMoneySerializerが評価され、それがカスタムシリアライザに追加され、リロード時にはそこにとどまります。

さらに、railtieやエンジンは、モジュールを含めることでフレームワーククラスを装飾することがあります。例えば、turbo-railsはこのようにActiveRecord::Baseを装飾します。

initializer "turbo.broadcastable" do
  ActiveSupport.on_load(:active_record) do
    include Turbo::Broadcastable
  end
end

これにより、Turbo::BroadcastableActiveRecord::Baseの祖先チェーンにモジュールオブジェクトが追加されます。リロードされた場合、Turbo::Broadcastableの変更は効果を持ちません。祖先チェーンは元のものを保持したままです。

推論:これらのクラスやモジュールはリロード可能ではありません

起動時にこれらのクラスやモジュールを参照する最も簡単な方法は、オートロードパスに属していないディレクトリでそれらを定義することです。例えば、libは典型的な選択肢です。デフォルトではオートロードパスには属していませんが、$LOAD_PATHには属しています。通常のrequireを使用してそれを読み込んでください。 上記のように、別のオプションは、autoloadでディレクトリを定義するディレクトリに配置することです。詳細については、config.autoload_once_pathsのセクションをご確認ください。

9.3 ユースケース3:エンジンのアプリケーションクラスを設定する

エンジンがユーザーをモデル化するリロード可能なアプリケーションクラスで動作し、その設定ポイントがあると仮定しましょう。

# config/initializers/my_engine.rb
MyEngine.configure do |config|
  config.user_model = User # NameError
end

リロード可能なアプリケーションコードとの互換性を確保するために、エンジンはアプリケーションがそのクラスの「名前」を設定する必要があります。

# config/initializers/my_engine.rb
MyEngine.configure do |config|
  config.user_model = "User" # OK
end

その後、実行時にconfig.user_model.constantizeを使用して現在のクラスオブジェクトを取得できます。

10 イーガーローディング

本番のような環境では、アプリケーションが起動するときにすべてのアプリケーションコードをロードする方が一般的には良いです。イーガーローディングは、リクエストをすぐに処理できるようにすべてのコードをメモリに読み込むため、またCoWにも対応しています。

イーガーローディングは、フラグconfig.eager_loadによって制御されます。このフラグは、production以外のすべての環境でデフォルトで無効になっています。Rakeタスクが実行されると、config.rake_eager_loadによってconfig.eager_loadが上書きされます。デフォルトでは、本番環境ではRakeタスクはアプリケーションをイーガーロードしません。

ファイルのイーガーロードの順序は未定義です。

イーガーローディング中、RailsはZeitwerk::Loader.eager_load_allを呼び出します。これにより、Zeitwerkで管理されるすべてのgemの依存関係もイーガーロードされます。

11 シングルテーブル継承

シングルテーブル継承は、遅延ロードとはうまく動作しません:Active Recordは正しく動作するためにSTIの階層を認識する必要がありますが、遅延ロードではクラスは必要に応じて正確にロードされます!

この根本的な不一致に対処するために、STIをプリロードする必要があります。さまざまなトレードオフを伴ういくつかのオプションがあります。それらを見てみましょう。

11.1 オプション1:イーガーローディングを有効にする

STIをプリロードする最も簡単な方法は、次のようにイーガーローディングを有効にすることです:

config.eager_load = true

config/environments/development.rbおよびconfig/environments/test.rbに設定します。

これはシンプルですが、アプリケーション全体を起動時およびリロード時にイーガーロードするため、コストがかかる場合があります。ただし、小規模なアプリケーションでは、このトレードオフは有益な場合があります。

11.2 オプション2:折りたたまれたディレクトリをプリロードする

階層を定義するファイルを専用のディレクトリに格納する方法です。これは概念的にも意味があります。このディレクトリは名前空間を表すためではなく、STIをグループ化するためのものです。

app/models/shapes/shape.rb
app/models/shapes/circle.rb
app/models/shapes/square.rb
app/models/shapes/triangle.rb

この例では、app/models/shapes/circle.rbShapes::CircleではなくCircleを定義することを望んでいます。これは、事前にコードベースをリファクタリングする必要がないため、シンプルにするための個人的な選択肢かもしれません。Zeitwerkの折りたたみ機能を使用すると、これを実現できます:

# config/initializers/preload_stis.rb

shapes = "#{Rails.root}/app/models/shapes"
Rails.autoloaders.main.collapse(shapes) # 名前空間ではありません。

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir(shapes)
  end
end

このオプションでは、これらの数少ないファイルを起動時にイーガーロードし、STIが使用されていなくてもリロードします。ただし、アプリケーションに多くのSTIがない限り、これには計測可能な影響はありません。

Zeitwerk::Loader#eager_load_dirメソッドは、Zeitwerk 2.6.2で追加されました。古いバージョンでは、app/models/shapesディレクトリをリストアップし、その内容にrequire_dependencyを呼び出すこともできます。

STIからモデルが追加、変更、削除される場合、リロードは期待どおりに動作します。ただし、新しい別のSTI階層がアプリケーションに追加される場合は、イニシャライザを編集してサーバーを再起動する必要があります。

11.3 オプション3:通常のディレクトリをプリロードする

前のオプションと同様ですが、ディレクトリは名前空間を表すことを意図しています。つまり、app/models/shapes/circle.rbShapes::Circleを定義することが期待されています。

これについては、折りたたみは設定されていないため、イニシャライザは同じです:

# config/initializers/preload_stis.rb

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir("#{Rails.root}/app/models/shapes")
  end
end

同じトレードオフです。

11.4 オプション4:データベースからタイプをプリロードする

このオプションでは、ファイルを任意の方法で整理する必要はありませんが、データベースにアクセスします:

# config/initializers/preload_stis.rb

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    types = Shape.unscoped.select(:type).distinct.pluck(:type)
    types.compact.each(&:constantize)
  end
end

テーブルにすべてのタイプがない場合でも、STIは正しく動作しますが、subclassesdescendantsなどのメソッドは欠落しているタイプを返しません。

STIからモデルが追加、変更、削除される場合、リロードは期待どおりに動作します。ただし、新しい別のSTI階層がアプリケーションに追加される場合は、イニシャライザを編集してサーバーを再起動する必要があります。

12 カスタマイズインフレクション

デフォルトでは、RailsはString#camelizeを使用して、特定のファイルやディレクトリ名がどの定数を定義するかを判断します。例えば、posts_controller.rb"posts_controller".camelizeが返すPostsControllerを定義する必要があります。

特定のファイルやディレクトリ名が望むようにインフレクトされない場合があります。例えば、html_parser.rbはデフォルトではHtmlParserを定義することが期待されます。しかし、クラス名をHTMLParserにしたい場合はどうでしょうか?これをカスタマイズする方法はいくつかあります。

最も簡単な方法は、略語を定義することです:

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym "HTML"
  inflect.acronym "SSL"
end

これにより、Active Supportのインフレクションがグローバルに影響を与えます。これは一部のアプリケーションでは問題ありませんが、デフォルトのインフレクターから独立して個々のベース名をキャメライズする方法をカスタマイズすることもできます。これはオーバーライドのコレクションをデフォルトのインフレクターに渡すことで行います:

Rails.autoloaders.each do |autoloader|
  autoloader.inflector.inflect(
    "html_parser" => "HTMLParser",
    "ssl_error"   => "SSLError"
  )
end

ただし、このテクニックは、デフォルトのインフレクターがフォールバックとして使用するString#camelizeに依存しているため、それに依存しないようにする場合は、Active Supportのインフレクションに依存せずにインフレクションを完全に制御することができます。これを行うには、インフレクターをZeitwerk::Inflectorのインスタンスに設定します:

Rails.autoloaders.each do |autoloader|
  autoloader.inflector = Zeitwerk::Inflector.new
  autoloader.inflector.inflect(
    "html_parser" => "HTMLParser",
    "ssl_error"   => "SSLError"
  )
end

これらのインスタンスに影響を与えるグローバルな設定はありません。これらは決定論的です。

さらなる柔軟性のために、カスタムインフレクターを定義することもできます。詳細については、Zeitwerkのドキュメントを参照してください。

12.1 インフレクションカスタマイズはどこに配置すべきですか?

アプリケーションがonceオートローダーを使用していない場合、上記のスニペットはconfig/initializersに配置できます。例えば、Active Supportの場合はconfig/initializers/inflections.rb、その他の場合はconfig/initializers/zeitwerk.rbです。

onceオートローダーを使用しているアプリケーションでは、この設定をconfig/application.rbのアプリケーションクラスの本体から移動またはロードする必要があります。なぜなら、onceオートローダーはブートプロセスの早い段階でインフレクターを使用するためです。

13 カスタム名前空間

前述のように、オートロードパスはトップレベルの名前空間であるObjectを表します。

例えば、app/servicesを考えてみましょう。このディレクトリはデフォルトでは生成されませんが、存在する場合、Railsは自動的にオートロードパスに追加します。

デフォルトでは、ファイルapp/services/users/signup.rbUsers::Signupを定義することが期待されますが、そのサブツリー全体をServices名前空間の下に配置したい場合はどうでしょうか?デフォルトの設定では、サブディレクトリapp/services/servicesを作成することでこれを実現できます。

ただし、好みによっては、app/services/users/signup.rbが単にServices::Users::Signupを定義することが望ましい場合もあります。

このような場合に対応するために、Zeitwerkはカスタムルート名前空間をサポートしており、mainオートローダーをカスタマイズすることでこれを実現できます:

# config/initializers/autoloading.rb

# 名前空間は存在している必要があります。
#
# この例では、モジュールをその場で定義していますが、他の場所で作成され、通常の`require`でここに定義をロードすることもできます。
# いずれにせよ、`push_dir`はクラスまたはモジュールオブジェクトを期待します。
module Services; end

Rails.autoloaders.main.push_dir("#{Rails.root}/app/services", namespace: Services)

Rails 7.1未満では、この機能はサポートされていませんが、同じファイルにこの追加のコードを追加して動作させることができます:

# Rails 7.1未満のアプリケーションの追加コード。
app_services_dir = "#{Rails.root}/app/services" # 文字列である必要があります
ActiveSupport::Dependencies.autoload_paths.delete(app_services_dir)
Rails.application.config.watchable_dirs[app_services_dir] = [:rb]

カスタム名前空間はonceオートローダーでもサポートされています。ただし、onceオートローダーはブートプロセスの早い段階で設定されるため、アプリケーションイニシャライザーでは設定できません。代わりに、例えばconfig/application.rbに配置してください。

14 オートロードとエンジン

エンジンは親アプリケーションのコンテキストで実行され、そのコードは親アプリケーションによってオートロード、リロード、イーガーロードされます。アプリケーションがzeitwerkモードで実行されている場合、エンジンのコードはzeitwerkモードでロードされます。アプリケーションがclassicモードで実行されている場合、エンジンのコードはclassicモードでロードされます。

Railsが起動すると、エンジンディレクトリがオートロードパスに追加され、オートローダーの観点からは違いがありません。オートローダーの主な入力はオートロードパスであり、それがアプリケーションのソースツリーに属しているか、エンジンのソースツリーに属しているかは関係ありません。

例えば、このアプリケーションはDeviseを使用しています:

% bin/rails runner 'pp ActiveSupport::Dependencies.autoload_paths'
[".../app/controllers",
 ".../app/controllers/concerns",
 ".../app/helpers",
 ".../app/models",
 ".../app/models/concerns",
 ".../gems/devise-4.8.0/app/controllers",
 ".../gems/devise-4.8.0/app/helpers",
 ".../gems/devise-4.8.0/app/mailers"]

エンジンが親アプリケーションのオートロードモードを制御する場合、エンジンは通常どおりに記述できます。 ただし、エンジンがRails 6またはRails 6.1をサポートし、親アプリケーションを制御していない場合、classicモードまたはzeitwerkモードのいずれかで実行できるように準備する必要があります。考慮すべき事項は次のとおりです。

  1. classicモードでは、ある時点で特定の定数がロードされることを保証するためにrequire_dependency呼び出しを行う必要があります。zeitwerkモードでは必要ありませんが、zeitwerkモードでも動作するので問題ありません。

  2. classicモードでは、定数名をアンダースコアで区切ります("User" -> "user.rb")、zeitwerkモードではファイル名をキャメルケースにします("user.rb" -> "User")。ほとんどの場合は一致しますが、"HTMLParser"のような連続する大文字の場合は一致しません。互換性を確保するためには、そのような名前を避けることです。この場合、"HtmlParser"を選択してください。

  3. classicモードでは、ファイルapp/model/concerns/foo.rbFooConcerns::Fooの両方を定義することができます。zeitwerkモードでは、Fooを定義する必要があります。互換性を確保するためには、Fooを定義してください。

15 テスト

15.1 手動テスト

タスクzeitwerk:checkは、プロジェクトツリーが期待される命名規則に従っているかどうかをチェックし、手動でチェックするのに便利です。たとえば、classicモードからzeitwerkモードに移行する場合や、何か修正する場合などです。

% bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!

アプリケーションの設定によっては、追加の出力がある場合がありますが、最後の「All is good!」が目的の出力です。

15.2 自動テスト

テストスイートでプロジェクトが正しくイーガーロードされることを確認するのは良い習慣です。

これにより、Zeitwerkの命名規則の遵守やその他のエラー条件を確認できます。Testing Rails Applicationsガイドのイーガーローディングのテストに関するセクションを確認してください。

16 トラブルシューティング

ローダーの動作を追跡する最良の方法は、そのアクティビティを検査することです。

最も簡単な方法は、config/application.rbのフレームワークのデフォルトをロードした後に

Rails.autoloaders.log!

を追加することです。これにより、トレースが標準出力に出力されます。

ファイルにログを出力する場合は、次のように設定してください。

Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")

config/application.rbが実行される時点では、Railsのロガーはまだ利用できません。Railsのロガーを使用する場合は、代わりに初期化子でこの設定を行ってください。

# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger

17 Rails.autoloaders

アプリケーションを管理するZeitwerkのインスタンスは、次の場所で利用できます。

Rails.autoloaders.main
Rails.autoloaders.once

述語

Rails.autoloaders.zeitwerk_enabled?

はRails 7アプリケーションでも利用可能であり、trueを返します。

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。