Syndication Icon
Published September 10, 2021 Updated September 11, 2021
Cover
Ruby Zeitwerk

Zeitwerk is a Ruby gem for auto-loading/reloading of objects within your project and will soon be a core part of Hanami 2.0.0 and Rails 7.0.0. To quote directly from the Zeitwerk’s documentation:

Given a conventional file structure, Zeitwerk is able to load 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, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby’s semantics for constants.

With the above in mind, I want to focus on using Zeitwerk within pure Ruby projects and/or gems and how Rubysmith can speed up this process for you. ⚡️

Table of Contents

Usage

When working with Ruby projects and gems there are two structures to keep in mind which depend on the project and gem name chosen.

Basic

Basic structures consist of either a standard or underscored project name. For example, if you name your project demo, you’d use the following structure:

demo
├── lib
│  └── demo.rb

…​which would result in the following implementation of demo.rb:

require "zeitwerk"

Zeitwerk::Loader.for_gem.setup

# Main namespace.
module Demo
end

Same goes underscored project names like, for example, demo_test:

demo_test
├── lib
│  └── demo_test.rb

…​which would result in the following implementation of demo_test.rb:

require "zeitwerk"

Zeitwerk::Loader.for_gem.setup

# Main namespace.
module DemoTest
end

Notice that in both of the above cases, each project is properly titleized/camelcased appropriately:

  • Titleize: demoDemo

  • Camelcase: demo_testDemoTest

So far I’ve been talking about Ruby projects and gems interchangeably. The reason is that no matter if you are building a pure, stand-alone Ruby project or a Ruby gem, the structure is the same. This is why Zeitwerk::Loader.for_gem is a convenient shortcut for both situations.

Nested

There is a slight caveat, though. Behavior is altered when a project name use dashes. Instead, we get a nested directory and object namespace. For example, if you name your project demo-test, you’ll end up with the following structure:

demo-test
├── lib
│  └── demo
│     └── test.rb

…​which results in the following implementation of demo/test.rb:

require "zeitwerk"

Zeitwerk::Loader.new
                .tap { |loader| loader.push_dir "#{__dir__}/.." }
                .setup

module Demo
  # Main namespace.
  module Test
  end
end

Notice — due to the dash in the project name — we have Test nested within the Demo namespace. Additionally, the corresponding file is structured as demo/test.rb. Finally, we have to teach Zeitwerk to load the project one directory up from where where Zeitwerk is initialized in order to load the entire library.

This is a conventional and standard practice for organizing Ruby projects and gems. You can find this enforced via the following gems:

  • Bundler - Default with all Ruby installations which can be handy for quick and dirty building of gems. Example usage: bundle gem demo.

  • Gemsmith - Built for professional gem smithing and a step above what you get with Bundler. Example usage: gemsmith --generate demo.

  • Rubysmith - Focused specifically on building Ruby projects only. Example usage: rubysmith --build demo.

All three of the gems above share the same behavior as Zeitwerk when it comes to conventional structures for Ruby projects.

Rubysmith

Now that you understand conventional structures, we can talk about how Rubysmith can automate your workflow further when building new projects. The great news is Rubysmith has Zeitwerk support built in by default. 🎉 This means you can use Rubysmith to build any of the following example projects:

rubysmith --build demo
rubysmith --build demo_test
rubysmith --build demo-test

You’ll get identical structures, as talked about earlier, each complete with Zeitwerk support by default. In situations you don’t desire Zeitwerk support, you can disable Zeitwerk when building a project. Example:

rubysmith --build demo --no-zeitwerk

…​which yields the following implementation within the lib/demo.rb file:

# Main namespace.
module Demo
end

Without Zeitwerk support, you’ll have to manually add require statements as you build out more of our implementation. There are definitely use cases where you want this behavior, especially when working on low-level gems where you want few dependencies. For the most part, letting Zeitwerk do the heavy lifting is a nice win and Rubysmith has you covered in that regard!

Conclusion

I hope you’ve enjoyed this brief exploration of the Zeitwerk gem for use in your own Ruby projects. Consider giving Rubysmith a try the next time you are building a Ruby project. You might be pleasantly surprised in the productivity boost you gain from having a tool like this in your back pocket.