This blog is part of our Rails 6 series. Rails 6.0 was recently released.

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.

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

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.

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 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 Mode

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.

Conclusion

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.