Railsの自動読み込み eager_loadの処理を調べた
eager_loadがどのように行われているのか知りたいので調べた。 環境は
eager_loadの処理
Railsのサーバーを立ち上げ時に config.ru ファイルが読み込まれる。
# config.ru # This file is used by Rack-based servers to start the application. require_relative 'config/environment' run Rails.application
config.ruはconfig/environment.rbを読み込む。
# config/environment.rb # frozen_string_literal: true # Load the Rails application. require_relative 'application' # Initialize the Rails application. Rails.application.initialize!
config/environment.rbは config/application.rb を読み込み、アプリケーションの設定などを確保する。
その後 Rails.application.initialize!
を実行する。 initialize!
から処理がRailsの内部に入っていく。
# https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/application.rb#L359-L366 # Initialize the application passing the given group. By default, the # group is :default def initialize!(group = :default) #:nodoc: raise "Application has been already initialized." if @initialized run_initializers(group, self) @initialized = true self end
実際の初期化処理は run_initializers
が行っている。
# https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/initializable.rb#L58-L64 def run_initializers(group = :default, *args) return if instance_variable_defined?(:@ran) initializers.tsort_each do |initializer| initializer.run(*args) if initializer.belongs_to?(group) end @ran = true end
最後にinitializeしたというフラグを @ran
に格納している。すでに実行済みであれば再実行されないようになっている。
initializers.tsort_each
のinitializersは Rails::Application#initializers
を実行している。
※ run_initializersが定義されているRails::Initializeableにも #initializers
メソッドはあるが、Rails::Application#initializersが実行されるのはメソッド探索時にRails::Application#initializersが先に見つかるから
# https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/application.rb#L368-L372 def initializers #:nodoc: Bootstrap.initializers_for(self) + railties_initializers(super) + Finisher.initializers_for(self) end
Bootstrap.initializers_for
- 起動時の初期化処理
- https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/initializable.rb#L84-L86
Rails::Application::Bootstrap
がRails::Initializeable
を継承している
railties_initializers
Finisher.initializers_for
- それ以外の初期化処理
- https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/initializable.rb#L84-L86
Rails::Application::Finisher
がRails::Initializeable
を継承している
eager_loadの処理はFinisherに定義されてる
# https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/application/finisher.rb#L116-L125 initializer :eager_load! do if config.eager_load ActiveSupport.run_load_hooks(:before_eager_load, self) # Checks defined?(Zeitwerk) instead of zeitwerk_enabled? because we # want to eager load any dependency managed by Zeitwerk regardless of # the autoloading mode of the application. Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk) config.eager_load_namespaces.each(&:eager_load!) end end
config.eager_load
は config/environments/development.rb とかで代入している config.eager_load
の値が参照される。
まず ActiveSupport.run_load_hooks(:before_eager_load, self)
でbefore_eager_loadという名前に登録されたブロックをselfをselfのコンテキストで実行する。
その後 Zeitwerk::Loader.eager_load_all
が実行される(autoloaderがzeitwerkであろうと、clasicだろうと関係なくZeitwerk::Loader.eager_load_allが実行される必要があるらしいが、詳しくどういう理由なのはわからなかった。zeitwerk gemが必要としているのかな)。
config.eager_load_namespaces.each(&:eager_load!)
ではeager_loadする名前空間をそれぞれeager_loadします。
[1] pry(main)> TestApp::Application => [I18n, ActiveSupport, ActionDispatch, ActiveModel, GlobalID, ActionView::Railtie, ActionView, ActionController, ActiveRecord, ActionMailer, ActionCable::Engine, ActionCable, Bootstrap::Rails::Engine, Devise::Engine, TestApp::Application]
eager_load!
の実体は下記
# https://github.com/rails/rails/blob/v6.0.2/railties/lib/rails/engine.rb#L472-L483 def eager_load! # Already done by Zeitwerk::Loader.eager_load_all in the finisher. return if Rails.autoloaders.zeitwerk_enabled? config.eager_load_paths.each do |load_path| # Starts after load_path plus a slash, ends before ".rb". relname_range = (load_path.to_s.length + 1)...-3 Dir.glob("#{load_path}/**/*.rb").sort.each do |file| require_dependency file[relname_range] end end end
zeitwerkを使っている場合はreturn、classicの場合はrequire_dependencyを使ってファイルをロードする。
参考
memo
require, require_dependencyの違い
require(path)
- pathが絶対パスのときはそのパスのファイルを読み込む
- 相対パスのときは
$LOAD_PATH
内のパスを順番に探して最初に見つかったファイルをロードする - 拡張子は補完される
- .rb, .so,.o,.dll など
- .rbが補完される
- 同じファイルを複数回読み込む
require_dependency(path)
- rails(active_support)のメソッド
- production環境ではrequire, development環境ではloadが実行される