Efficient and thread-safe code loader for Ruby
Zeitwerk is an efficient and thread-safe code loader for Ruby.
Given a conventional file structure, Zeitwerk is capable of loading your project's classes and modules on demand (autoloading) or upfront (eager loading). You don't need to write require calls for your own files; instead, you can streamline your programming by knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and aligns with Ruby's semantics for constants.
Zeitwerk also supports code reloading, which can be useful during web application development. However, coordination is required to reload in a thread-safe manner. The documentation below explains how to achieve this.
The gem is designed to allow any project, gem dependency, or application to have its own independent loader. Multiple loaders can coexist in the same process, each managing its own project tree and operating independently of each other. Each loader has its own configuration, inflector, and optional logger.
Internally, Zeitwerk exclusively uses absolute file names when issuing require calls, eliminating the need for costly file system lookups in $LOAD_PATH. Technically, the directories managed by Zeitwerk don't even need to be in $LOAD_PATH.
Furthermore, Zeitwerk performs a single scan of the project tree at most, lazily descending into subdirectories only when their namespaces are used.
Main interface for gems:
# lib/my_gem.rb (main file)
require 'zeitwerk'
loader = Zeitwerk::Loader.for_gem
loader.setup # ready!
module MyGem
# ...
end
loader.eager_load # optionallyMain generic interface:
loader = Zeitwerk::Loader.new
loader.push_dir(...)
loader.setup # ready!The loader variable can go out of scope. Zeitwerk keeps a registry with all of them, and so the object won't be garbage collected.
You can reload if you want to:
loader = Zeitwerk::Loader.new
loader.push_dir(...)
loader.enable_reloading # you need to opt-in before setup
loader.setup
...
loader.reloadand you can eager load all the code:
loader.eager_loadIt is also possible to broadcast eager_load to all instances:
Zeitwerk::Loader.eager_load_allFor Zeitwerk to work with your file structure, simply name files and directories after the classes and modules they define:
lib/my_gem.rb -> MyGem
lib/my_gem/foo.rb -> MyGem::Foo
lib/my_gem/bar_baz.rb -> MyGem::BarBaz
lib/my_gem/woo/zoo.rb -> MyGem::Woo::ZooYou can fine-tune this behavior by collapsing directories or ignoring specific parts of the project, but that is the main idea.
While a simple constant like HttpCrawler::MAX_RETRIES can be defined in its own file:
# http_crawler/max_retries.rb
HttpCrawler::MAX_RETRIES = 10that is not required, you can also define it the regular way:
# http_crawler.rb
class HttpCrawler
MAX_RETRIES = 10
endThe first example needs a custom inflection rule:
loader.inflector.inflect('max_retries' => 'MAX_RETRIES')Otherwise, Zeitwerk would expect the file to define MaxRetries.
In the second example, no custom rule is needed.
Every directory configured with push_dir is called a root directory, and they represent root namespaces.
ObjectBy default, the namespace associated to a root directory is the top-level one: Object.
For example, given
loader.push_dir("#{__dir__}/models")
loader.push_dir("#{__dir__}/serializers")these are the expected classes and modules being defined by these files:
models/user.rb -> User
serializers/user_serializer.rb -> UserSerializerAlthough Object is the most common root namespace, you have the flexibility to associate a different one with a specific root directory. The push_dir method accepts a non-anonymous class or module object as the optional namespace keyword argument.
For example, given:
require 'active_job'
require 'active_job/queue_adapters'
loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)a file defining ActiveJob::QueueAdapters::MyQueueAdapter does not need the conventional parent directories, you can (and have to) store the file directly below adapters:
adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapterPlease note that the provided root namespace must be non-reloadable, while allowing autoloaded constants within that namespace to be reloadable. This means that if you associate the app/api directory with an existing Api module, the module itself should not be reloadable. However, if the project defines and autoloads the Api::Deliveries class, that class can be reloaded.
Root directories are recommended not to be nested; however, Zeitwerk provides support for nested root directories since in frameworks like Rails, both app/models and app/models/concerns belong to the autoload paths.
Zeitwerk identifies nested root directories and treats them as independent roots. In the given example, concerns is not considered a namespace within app/models. For instance, consider the following file:
app/models/concerns/geolocatable.rbshould define Geolocatable, not Concerns::Geolocatable.
If a namespace consists only of a simple module without any code, there is no need to explicitly define it in a separate file. Zeitwerk automatically creates modules on your behalf for directories without a corresponding Ruby file.
For instance, suppose a project includes an admin directory:
app/controllers/admin/users_controller.rb -> Admin::UsersControllerand does not have a file called admin.rb, Zeitwerk automatically creates an Admin module on your behalf the first time Admin is used.
To trigger this behavior, the directory must contain non-ignored Ruby files with the ".rb" extension, either directly or recursively. Otherwise, the directory is ignored. This condition is reevaluated during reloads.
Classes and modules that act as namespaces can also be explicitly defined in a file. This can be done with ordinary files named after the corresponding constant path, or with special namespace files, or nsfiles for short.
Let's consider:
app/models/hotel.rb -> Hotel
app/models/hotel/pricing.rb -> Hotel::PricingSince there is a file app/models/hotel.rb and also a directory app/models/hotel, Zeitwerk realizes Hotel is a namespace that is defined in app/models/hotel.rb.
In order to realize this, the directory or directories conforming the namespace do not need to be next to the file, as in the example, they could be in some other root directory.
The classes and modules from an explicit namespace are already available in the body of the class or module that defines it:
class Hotel < ApplicationRecord
include Pricing # works
...
endWhen autoloaded, Zeitwerk verifies the expected constant (Hotel in the example) stores a class or module object. If it doesn't, Zeitwerk::Error is raised.
If the
No open issues yet, or sync has not completed.