October 8, 2019
This blog is part of our Rails 6 series.
Zeitwerk is the new code loader that
comes with Rails 6 by default.
In addition to providing
autoloading, eager loading, and reloading capabilities,
it also improves the classical code loader by being efficient and thread safe.
According to the author of Zeitwerk, Xavier Noria,
one of the main motivations for writing Zeitwerk was to keep code DRY and to
remove the brittle require
calls.
Zeitwerk is available as a gem with no additional dependencies. It means any regular Ruby project can use Zeitwerk.
Zeitwerk is baked in a Rails 6 project, thanks to the Zeitwerk-Rails integration. For a non-Rails project, adding the following into the project's entry point sets up Zeitwerk.
loader = Zeitwerk::Loader.new
loader.push_dir(...)
loader.setup
For gem maintainers, Zeitwerk provides the handy .for_gem
utility method
The following example from Zeitwerk documentation illustrates the usage of
Zeitwerk.for_gem
method.
#lib/my_gem.rb (main file)
require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup
module MyGem
# Since the setup has been performed, at this point we are already
# able to reference project constants, in this case MyGem::MyLogger.
include MyLogger
end
Before we look into Zeitwerk's internals, the following section provides a quick refresher on constant-resolution in Ruby and how classical code loader of Rails works.
Ruby's constant resolution looks for a constant in the following places.
It triggers 'constant_missing' callback when it can't find the constant.
Ruby used to look for constants in Object.ancestors as well, but that seems not the case anymore. An in-depth explanation of constant resolution can be found at Conrad Irwin's blog.
Classical code loader (code loader in Rails version < 6.0) achieves autoloading by overriding Module#const_missing and loads the missing constant without the need for an explicit require call as long as the code follows certain conventions.
Zeitwerk takes an entirely different approach in autoloading by registering constants to be autoloaded by Ruby.
Consider the following configuration in which Zeitwerk manages lib
directory
and lib
has automobile.rb
file.
loader.push_dir('./lib')
Zeitwerk then uses Module.autoload to tell Ruby that "Automobile" can be found in "lib/automobile.rb".
autoload "Automobile", "lib/automobile.rb"
Unlike classical loader, Zeitwerk takes module nesting into account while loading constants by leveraging the new Tracepoint API to go look for constants defined in subdirectories when a new class or module is defined.
Let us look at an example to understand this better.
class Automobile
# => Tracepoint hook triggers here.
# include Engine
end
When
the tracepoint hook
triggers, Zeitwerk checks for an automobile
directory in the same level as
automobile.rb and sets up Module.autoload for that directory and all the files
(in this case ./automobile/engine.rb) within that directory.
Previously in Rails, we had a code loader that was riddled with gotchas and struggled to be thread safe. Zeitwerk does a better job by leveraging the new Ruby standard API and matches Ruby's semantics for constants.
If this blog was helpful, check out our full blog archive.