rails/rails
Autoloading
Rails uses Zeitwerk for autoloading and code reloading. This page documents the integration: which loaders exist, how the autoload paths are configured, and how reloading works in development.
Two loaders
Rails::Autoloaders (railties/lib/rails/autoloaders.rb) configures two Zeitwerk loaders per application:
main— for code that participates in development reloading. Drivesapp/, thelib/paths the app added, and engineapp/paths.once— for code that loads at boot and is not reloaded. Used for engine internals and anything declared viaconfig.autoload_once_paths.
# railties/lib/rails/autoloaders.rb
class Rails::Autoloaders
def initialize
@main = Zeitwerk::Loader.new
@once = Zeitwerk::Loader.new
@main.tag = "rails.main"
@once.tag = "rails.once"
end
endBoth loaders share a single inflector configured from Rails.autoloaders.main.inflector.
Autoload paths
Three configurable arrays in Rails::Engine::Configuration:
autoload_paths— directories Zeitwerk will scan, reloaded in dev. Defaults toapp/*(app/models,app/controllers, etc.) andapp/*/concerns.autoload_once_paths— directories that must be loaded once and never reloaded.eager_load_paths— directories that get fully required whenconfig.eager_load = true(production by default).
add_lib_to_load_path! (in application.rb) prepends lib/ to $LOAD_PATH even if lib/ isn't in any autoload list. By default in 7.x+, Rails does not add lib/ to autoload paths automatically — apps opt in via config.autoload_lib(ignore: %w[assets tasks]).
Boot integration
The autoloaders are set up by initializers in railties/lib/rails/application/bootstrap.rb and the engine's own initializers:
graph TD
A[Engine#initialize_eager_load] --> B[Compute eager_load_paths]
B --> C[Add to Zeitwerk loaders]
C --> D[Eager load if production / config.eager_load]
D --> E[App ready]For each engine's autoload paths, Rails.autoloaders.main (or once) calls loader.push_dir(path). Then loader.setup finalizes registration with Zeitwerk's underlying tracepoint.
Reloading in development
When config.enable_reloading = true (the dev default), ActiveSupport::Reloader watches files via ActiveSupport::EventedFileUpdateChecker (or FileUpdateChecker as a fallback). On every request:
sequenceDiagram
participant MW as ActionDispatch::Reloader
participant Reloader as ActiveSupport::Reloader
participant Files as FileUpdateChecker
participant Z as Zeitwerk
MW->>Reloader: prepare!
Reloader->>Files: any updated?
alt files changed
Files-->>Reloader: yes
Reloader->>Z: reload!
Z->>Z: unload all autoloaded constants
Z->>Reloader: ready for next load
else no change
Files-->>Reloader: no
end
Reloader-->>MW: readyZeitwerk::Loader#reload is what actually unloads the autoloaded constants. The to_prepare callbacks run after every reload to re-initialize state that depends on autoloaded classes.
Conventions Zeitwerk requires
Zeitwerk is strict about the file-name to constant-name mapping:
app/models/user.rb→Userapp/models/admin/user.rb→Admin::Userapp/controllers/api/v1/posts_controller.rb→Api::V1::PostsController
Acronyms work via the inflector:
Rails.autoloaders.main.inflector.inflect("api" => "API", "v1" => "V1")config/initializers/inflections.rb is the standard place to add these.
eager_load
In production, config.eager_load = true triggers a recursive require of every constant in the eager_load_paths during boot. Implementation in railties/lib/rails/application/finisher.rb (:eager_load! initializer). Eager loading:
- Surfaces autoload errors at boot rather than at first request.
- Allows the framework to fork without copy-on-write penalties.
- Lets Bootsnap (
bootsnap-precompile) cache compiled bytecode.
bin/rails zeitwerk:check (in an app) verifies that every file in autoload paths can be loaded without errors and that constant names match file paths.
Eager load namespaces
A railtie or engine can mark parts of itself as eager loadable:
# railties/lib/rails/railtie.rb provides this
config.eager_load_namespaces << MyEngineThen Rails.application.eager_load! walks each namespace's autoload paths.
Custom loaders
Engines that need their own loader (e.g., to scan a non-standard layout) can declare:
class MyEngine::Engine < Rails::Engine
initializer "my_engine.add_loader" do |app|
Rails.autoloaders.once.push_dir("#{root}/lib/my_engine")
end
endRails.autoloaders.once is for engine internals — code that should be loaded once and not reloaded with the app.
Autoload helpers in components
Components use Active Support's autoload helpers (activesupport/lib/active_support/dependencies/autoload.rb) to declare lazy module loading at the framework level:
module ActiveRecord
extend ActiveSupport::Autoload
autoload :Base
autoload :Relation
autoload :Migration
endThese are not tied to Zeitwerk — they're a thin wrapper around Ruby's autoload keyword that expands relative paths against the calling file's directory.
Common pitfalls
- Constant naming mismatches. Zeitwerk insists on the file name matching the constant. Add inflections for acronyms.
- Modifying
$LOAD_PATHoutside autoload. Don'trequirefiles inside autoload paths; let Zeitwerk handle it. - Holding references across requests. A reloaded class is a new constant; old references point at stale classes.
lib/is not autoloaded by default in modern Rails. Useconfig.autoload_lib(ignore: %w[assets tasks])to opt in.
Entry points for modification
- The autoloader entry point is
railties/lib/rails/autoloaders.rb. - File-watching strategies are
activesupport/lib/active_support/file_update_checker.rbandevented_file_update_checker.rb. - The reloader is
activesupport/lib/active_support/reloader.rband the executor isactivesupport/lib/active_support/execution_wrapper.rb. - The middleware that wraps requests in the reloader is
actionpack/lib/action_dispatch/middleware/reloader.rb.
Related pages
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.