I love the autoload functionality of Ruby; however, it's going away in future versions of Ruby since it was never thread-safe.
So right now I would like to pretend it's already gone and write my code without it, by implementing the lazy-loading mechanism myself. I'd like to implement it in the simplest way possible (I don't care about thread-safety right now). Ruby should allow us to do this.
Let's start by augmenting a class' const_missing
:
class Dummy
def self.const_missing(const)
puts "const_missing(#{const.inspect})"
super(const)
end
end
Ruby will call this special method when we try to reference a constant under "Dummy" that's missing, for instance if we try to reference "Dummy::Hello", it will call const_missing
with the Symbol :Hello
. This is exactly what we need, so let's take it further:
class Dummy
def self.const_missing(const)
if :OAuth == const
require 'dummy/oauth'
const_get(const) # warning: possible endless loop!
else
super(const)
end
end
end
Now if we reference "Dummy::OAuth", it will require the "dummy/oauth.rb" file which is expected to define the "Dummy::OAuth" constant. There's a possibility of an endless loop when we call const_get
(since it can call const_missing
internally), but guarding against that is outside the scope of this question.
The big problem is, this whole solution breaks down if there exists a module named "OAuth" in the top-level namespace. Referencing "Dummy::OAuth" will skip its const_missing
and just return the "OAuth" from the top-level. Most Ruby implementations will also make a warning about this:
warning: toplevel constant OAuth referenced by Dummy::OAuth
This was reported as a problem way back in 2003 but I couldn't find evidence that the Ruby core team was ever concerned about this. Today, most popular Ruby implementations carry the same behavior.
The problem is that const_missing
is silently skipped in favor of a constant in the top-level namespace. This wouldn't happen if "Dummy::OAuth" was declared with Ruby's autoload
functionality. Any ideas how to work around this?
Rails automatically reloads classes and modules if application files in the autoload paths change. More precisely, if the web server is running and application files have been modified, Rails unloads all autoloaded constants managed by the main autoloader just before the next request is processed.
Ruby has an in-built module autoload, which comes into action whenever a specific module or a class is accessed or called upon from the parent or calling class or module. Upon receiving a call, this module registers the corresponding file path to the called module.
Zeitwerk is an efficient and thread-safe code loader for Ruby. Given a conventional file structure, Zeitwerk is able to load your project's classes and modules on demand (autoloading), or upfront (eager loading).
Modules provide a structure to collect Ruby classes, methods, and constants into a single, separately named and defined unit. This is useful so you can avoid clashes with existing classes, methods, and constants, and also so that you can add (mix in) the functionality of modules into your classes.
This was raised in a Rails ticket some time ago and when I investigated it there appeared to be no way round it. The problem is that Ruby will search the ancestors before calling const_missing
and since all classes have Object
as an ancestor then any top-level constants will always be found. If you can restrict yourself to only using modules for namespacing then it will work since they do not have Object
as an ancestor, e.g:
>> class A; end
>> class B; end
>> B::A
(irb):3: warning: toplevel constant A referenced by B::A
>> B.ancestors
=> [B, Object, Kernel, BasicObject]
>> module C; end
>> module D; end
>> D::C
NameError: uninitialized constant D::C
>> D.ancestors
=> [D]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With