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.
How to 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.
1 2loader = Zeitwerk::Loader.new 3loader.push_dir(...) 4loader.setup 5
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.
1#lib/my_gem.rb (main file) 2 3require "zeitwerk" 4loader = Zeitwerk::Loader.for_gem 5loader.setup 6 7module MyGem 8 # Since the setup has been performed, at this point we are already 9 # able to reference project constants, in this case MyGem::MyLogger. 10 include MyLogger 11end 12
How does Zeitwerk work?
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.
- In each entry of Module.nesting
- In each entry of Module.ancestors
It triggers 'constant_missing' callback when it can't find the constant.
Classical Code Loader in Rails
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.
- The file should be within a directory in ActiveSupport::Dependencies.autoload_paths
- A file should be named after the class, i.e Admin::RoutesController => admin/routes_controller.rb
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.
1 2loader.push_dir('./lib') 3
Zeitwerk then uses Module.autoload to tell Ruby that "Automobile" can be found in "lib/automobile.rb".
1 2autoload "Automobile", "lib/automobile.rb" 3
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.
1 2class Automobile 3 # => Tracepoint hook triggers here. 4 # include Engine 5end 6
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.