Xavier Noria Profile picture
Oct 11 1 tweets 2 min read Read on X
I just released Zeitwerk 2.7.

Now, you can define namespaces with manual constant assignments too (think, Namespace = Struct.new).

To the best of my knowledge, this completes the most important goal of the gem: to fully match Ruby. 🎉

Let me elaborate a bit on why that was not supported before, and how we have been able to remove that long-standing limitation.

First, let's take a step back to understand a tricky aspect of namespaces. Consider this Hotel namespace (in Ruby, both classes and modules can be namespaces):

# hotel.rb
class Hotel
include Pricing
end

# hotel/pricing.rb
module Hotel::Pricing
# Pricing related logic extracted to a mixin.
end

You cannot load any of the two files in regular Ruby, right? To load hotel.rb you need Hotel::Pricing in place, but if you try to load hotel/pricing.rb, you need Hotel!

But that works if the project is managed by Zeitwerk, and it worked with the classic autoloader too.

To support that use case, the :class event of TracePoint was instrumental, and it was the only technique I could think of in 2018.

It worked this way: When TracePoint notifies that Hotel has been created, the loader scans its subdirectories and defines autoloads (issues Module#autoload calls) for child constants, in particular for Hotel::Pricing.

The key observation is that happens precisely before the include line is executed. When the interpreter hits that line, the autoload is in place and autoloading hotel/pricing.rb is possible (because Hotel exists!).

Problem was, the event was not triggered for manual constant assignments (think Hotel = Class.new). So, while that was a corner case, unfortunately we could not achieve full parity with this technique.

The most important and common way to the define namespaces, using the class/module keywords, worked. But argghhhh.

But the suffering is over my friends!

In part motivated by this problem, @_byroot implemented Module#const_added. That callback is invoked in both situations! 🤘

Thanks to Module#const_added, the limitation has been finally removed. Also, Zeitwerk no longer uses TracePoint internally (/cc @yukihiro_matz).

Zeitwerk 2.7 requires Ruby 3.2 or later, and upgrading should be seamless for existing projects.

Don't worry if your app uses an older Ruby. Bundler realizes 2.7's Ruby version constraint is not satisfied, and ignores it in that case. Gemfile.lock will always get a version compatible with your app.

Same for gems that need to support Ruby versions older than 3.2, a version constraint for zeitwerk like ~> 2.6 will do the job. Client code will automatically use a compatible one.

Mission complete! 🎉

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Xavier Noria

Xavier Noria Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @fxn

Sep 30
People migrating Rails apps from classic to zeitwerk told me they needed an estimation.

The task zeitwerk:check eager loads. Name mismatches trigger built-in error logic, and you cannot continue because doing so would be unpredictable.

But you can get some sense of "progress":
1) In classic mode, eager load the application by setting config.eager_load = true in config/environments/development.rb.

Then, iterate over ActiveSupport::Dependencies.autoloaded_constants, and print their const_source_location.
2) After that, boot in zeitwerk mode with eager loading disabled and throw config.after_initialize { pp Rails.autoloaders.main.all_expected_cpaths } in config/application.rb.

(Needs Zeitwerk 2.6.14.)
Read 4 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us!

:(