Understanding Zeitwerk in Rails 6

Marcelo Casiraghi
Sep 26, 2019 · 5 min read
Image for post
Image for post

Zeitwerk is the new code loader engine used in Rails 6. It’s meant to be the new default for all Rails 6+ projects replacing the old classic engine.

If we judge by its features, Zeitwerk mode seems to behave pretty much the same as the previous classic mode. The basics are very similar: models, controllers, helpers and so forth will continue to be autoloaded, reloaded and eager loaded as they have always been.

However, the main difference resides in the implementation. Zeitwerk uses a better strategy for loading things, which solves all of the known gotchas that classic mode carries.

Also, something exciting is that Zeitwerk comes as a gem, which means you can leverage it inside other non-rails projects.

Why is it better?

Just like classic, Zeitwerk provides the features of code autoloading, eager loading, and reloading.

However, while classic mode relies on the callback for loading files, Zeitwerk uses Ruby’s native method.

Let’s explore the differences to understand why the latter is better.

How classic mode autoloads

Classic mode algorithm mainly relies on two factors: and the use of Ruby’s callback.

How does it work?

During code execution, every time Ruby finds an unknown constant reference a callback is fired. Rails overrides the default callback from Ruby, which would normally just raise a . Instead, it performs an attempt to load the file associated with the constant being looked up.

This is when comes into play. Rails walks through the list looking for the “snake case” version of the referenced constant and, if it exists, loads/requires the file.

If everything works well, the file is required, the constant gets defined, and the code execution continues.

This works fine, but it has a few limitations.

For example, there’s a well-known issue that comes with classic mode known as When constants aren’t missed. Basically, it describes how you can easily introduce code that depends on the order in which files are autoloaded to work properly.

How so? Let’s see a quick example. Consider the following:

# app/models/user.rb
class User < ApplicationRecord
end
# app/models/admin/user.rb
module Admin
class User < ApplicationRecord
end
end
# app/models/admin/user_manager.rb
module Admin
class UserManager
def self.all
User.all # Want to load all admin users
end
end
end

In this case, if the constant was already loaded at the time was called, then it would return objects.

However, if was not yet auto-loaded, but was, would instead return objects!

This is obviously highly undesirable, and the source of the problem comes from design: classic relies on the callback and this callback is only fired as the last step of the lookup algorithm. So by construction, you can’t avoid having to deal with these issues at the application level.

So, how does Zeitwerk fix this? Continue reading…

Zeitwerk autoloading

Similarly to classic mode, Zeitwerk also relies on two factors. The first is the list of (they are actually called root directories, but they are still referenced as in the context of Rails).

The second factor and the main difference with classic is that Zeitwerk relies on Ruby’s native method for triggering the autoloading of constants.

To understand it, let’s consider the following example. Let’s say you have a Rails 6 app with the following models:

app/
models/
comment.rb
post.rb

When the project boots, Rails will call . This method takes care of setting up the autoloaders for all of the known (they won’t be loaded yet though).

In the example, directory is included in the , so Zeitwerk will set up a for each one of the constants that are inferred by the name of the files inside that directory.

In this case, Zeitwerk will infer that and should define the constants and respectively.

So here’s the magic; Zeitwerk will execute the following code on your behalf:

autoload :Comment, Rails.root.join('app/models/comment.rb')
autoload :Post, Rails.root.join('app/models/post.rb')

It’s as simple as that! But, what does the above do? Let’s dig into how fits in this process.

Every time a Ruby program evaluates a constant there are certain steps that occur in order. For example, when Ruby evaluates the constant:

  • First, Ruby checks if there’s already a stored reference for in the symbol table. If there’s one, it returns the pointer to the class definition.
  • If it doesn’t find one, then Ruby checks if there’s an set up for
  • If there’s no autoload in place, then Ruby fires a callback.
  • However, if an is indeed defined (like in our example), Ruby the path that was passed in as an argument to the call () and then expects the required file to define

And just like that, the constant is auto-loaded!

This works great and is a better approach than classic. Why? Because is a built-in feature in Ruby, so instead of listening on and manually loading stuff (which can get hacky) we get to use the method that is served by the virtual machine to solve the very same purpose!

Note: Zeitwerk relies on the convention that each file will define the constant that is named after the name of the file (meaning that should define the constant). Luckily, this is not surprising for a normal Rails app.

Zeitwerk and Rails 6

Zeitwerk comes enabled with Rails 6 by default.

If you are upgrading, will set Zeitwerk as the default autoloader for your project and you’ll have it for free.

But, if for any reason you don’t want to use it, you can always opt-out by setting in your .

Gem usage

One of the greatest advantages of Zeitwerk is that it’s built as a separate gem from Rails. So, if you are writing or maintaining a gem, you can easily add Zeitwerk as a dependency and you’ll be able to forget about statements!

You can read the up-to-date doc here, but mainly you just need to do:

# lib/my_gem.rb (main file)

require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup # ready!

module MyGem
# ...
end

loader.eager_load # optionally

And that’s it!

Check this sample diff from the nanoc gem when they introduced Zeitwerk to their project: https://github.com/nanoc/nanoc/pull/1403/files and look at all the code they were able to delete.

To me, this approach is highly desirable because manually writing and maintaining is error-prone and fragile if you don’t do it carefully. With this gem, you can just forget all about it!

Thanks to Xavier Noria and all the contributors that put the Zeitwerk project together!

Cedarcode

Adding value from day one.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store