zeitwerk 2.2.2 → 2.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +176 -11
- data/lib/zeitwerk/explicit_namespace.rb +9 -8
- data/lib/zeitwerk/gem_inflector.rb +2 -4
- data/lib/zeitwerk/inflector.rb +4 -7
- data/lib/zeitwerk/kernel.rb +33 -2
- data/lib/zeitwerk/loader.rb +171 -124
- data/lib/zeitwerk/loader/callbacks.rb +27 -12
- data/lib/zeitwerk/real_mod_name.rb +1 -2
- data/lib/zeitwerk/registry.rb +12 -23
- data/lib/zeitwerk/version.rb +1 -1
- metadata +12 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f45a7c3caf48f06e10dd513a7b074da7ec059fa3299751d361514aadc83d995e
|
4
|
+
data.tar.gz: c8c29793e95245b47b1283891791d2c20365b8c694b3dcdfb7daab0b74c16bdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6194d326b268c9333ed9d2ac1c62b65756fa9af844c5fb7ad8272ecec33aa439015506758bcd329e421508facb4153e04e45da0efb0a2a42001a6f2e0a0d3b6a
|
7
|
+
data.tar.gz: 656009f40e777f641ff1dc80f54b63559514439538c73965aa9226b6c3e33c6be71c07eb30adfe7f4cd4b3be72aa6e42918392ba27167fb9502b9aed037f36d3
|
data/README.md
CHANGED
@@ -12,9 +12,12 @@
|
|
12
12
|
- [File structure](#file-structure)
|
13
13
|
- [Implicit namespaces](#implicit-namespaces)
|
14
14
|
- [Explicit namespaces](#explicit-namespaces)
|
15
|
+
- [Collapsing directories](#collapsing-directories)
|
15
16
|
- [Nested root directories](#nested-root-directories)
|
16
17
|
- [Usage](#usage)
|
17
18
|
- [Setup](#setup)
|
19
|
+
- [Generic](#generic)
|
20
|
+
- [for_gem](#for_gem)
|
18
21
|
- [Autoloading](#autoloading)
|
19
22
|
- [Eager loading](#eager-loading)
|
20
23
|
- [Reloading](#reloading)
|
@@ -22,6 +25,7 @@
|
|
22
25
|
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
23
26
|
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
24
27
|
- [Custom inflector](#custom-inflector)
|
28
|
+
- [The on_load callback](#the-on_load-callback)
|
25
29
|
- [Logging](#logging)
|
26
30
|
- [Loader tag](#loader-tag)
|
27
31
|
- [Ignoring parts of the project](#ignoring-parts-of-the-project)
|
@@ -29,8 +33,11 @@
|
|
29
33
|
- [Use case: The adapter pattern](#use-case-the-adapter-pattern)
|
30
34
|
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
31
35
|
- [Edge cases](#edge-cases)
|
36
|
+
- [Reopening third-party namespaces](#reopening-third-party-namespaces)
|
32
37
|
- [Rules of thumb](#rules-of-thumb)
|
33
|
-
- [
|
38
|
+
- [Debuggers](#debuggers)
|
39
|
+
- [Break](#break)
|
40
|
+
- [Byebug](#byebug)
|
34
41
|
- [Pronunciation](#pronunciation)
|
35
42
|
- [Supported Ruby versions](#supported-ruby-versions)
|
36
43
|
- [Testing](#testing)
|
@@ -51,7 +58,9 @@ Zeitwerk is also able to reload code, which may be handy while developing web ap
|
|
51
58
|
|
52
59
|
The gem is designed so that any project, gem dependency, application, etc. can have their own independent loader, coexisting in the same process, managing their own project trees, and independent of each other. Each loader has its own configuration, inflector, and optional logger.
|
53
60
|
|
54
|
-
Internally, Zeitwerk issues `require` calls exclusively using absolute file names, so there are no costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk do not even need to be in `$LOAD_PATH`.
|
61
|
+
Internally, Zeitwerk issues `require` calls exclusively using absolute file names, so there are no costly file system lookups in `$LOAD_PATH`. Technically, the directories managed by Zeitwerk do not even need to be in `$LOAD_PATH`.
|
62
|
+
|
63
|
+
Furthermore, Zeitwerk does at most one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
|
55
64
|
|
56
65
|
<a id="markdown-synopsis" name="synopsis"></a>
|
57
66
|
## Synopsis
|
@@ -131,6 +140,22 @@ app/models/user.rb -> User
|
|
131
140
|
app/controllers/admin/users_controller.rb -> Admin::UsersController
|
132
141
|
```
|
133
142
|
|
143
|
+
Alternatively, you can associate a custom namespace to a root directory by passing a class or module object in the optional `namespace` keyword argument.
|
144
|
+
|
145
|
+
For example, Active Job queue adapters have to define a constant after their name in `ActiveJob::QueueAdapters`.
|
146
|
+
|
147
|
+
So, if you declare
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
require "active_job"
|
151
|
+
require "active_job/queue_adapters"
|
152
|
+
loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
|
153
|
+
```
|
154
|
+
|
155
|
+
your adapter can be stored directly in that directory instead of the canonical `#{__dir__}/active_job/queue_adapters`.
|
156
|
+
|
157
|
+
Please, note that the given namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::V2::Deliveries`, that one can be reloaded.
|
158
|
+
|
134
159
|
<a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
|
135
160
|
### Implicit namespaces
|
136
161
|
|
@@ -165,6 +190,32 @@ end
|
|
165
190
|
|
166
191
|
An explicit namespace must be managed by one single loader. Loaders that reopen namespaces owned by other projects are responsible for loading their constants before setup.
|
167
192
|
|
193
|
+
<a id="markdown-collapsing-directories" name="collapsing-directories"></a>
|
194
|
+
### Collapsing directories
|
195
|
+
|
196
|
+
Say some directories in a project exist for organizational purposes only, and you prefer not to have them as namespaces. For example, the `actions` subdirectory in the next example is not meant to represent a namespace, it is there only to group all actions related to bookings:
|
197
|
+
|
198
|
+
```
|
199
|
+
booking.rb -> Booking
|
200
|
+
booking/actions/create.rb -> Booking::Create
|
201
|
+
```
|
202
|
+
|
203
|
+
To make it work that way, configure Zeitwerk to collapse said directory:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
loader.collapse("#{__dir__}/booking/actions")
|
207
|
+
```
|
208
|
+
|
209
|
+
This method accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
|
210
|
+
|
211
|
+
You can pass directories and glob patterns. Glob patterns are expanded when they are added, and again on each reload.
|
212
|
+
|
213
|
+
To illustrate usage of glob patterns, if `actions` in the example above is part of a standardized structure, you could use a wildcard:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
loader.collapse("#{__dir__}/*/actions")
|
217
|
+
```
|
218
|
+
|
168
219
|
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
169
220
|
### Nested root directories
|
170
221
|
|
@@ -184,6 +235,9 @@ should define `Geolocatable`, not `Concerns::Geolocatable`.
|
|
184
235
|
<a id="markdown-setup" name="setup"></a>
|
185
236
|
### Setup
|
186
237
|
|
238
|
+
<a id="markdown-generic" name="generic"></a>
|
239
|
+
#### Generic
|
240
|
+
|
187
241
|
Loaders are ready to load code right after calling `setup` on them:
|
188
242
|
|
189
243
|
```ruby
|
@@ -200,9 +254,36 @@ loader.push_dir(...)
|
|
200
254
|
loader.setup
|
201
255
|
```
|
202
256
|
|
203
|
-
|
257
|
+
<a id="markdown-for_gem" name="for_gem"></a>
|
258
|
+
#### for_gem
|
204
259
|
|
205
|
-
|
260
|
+
`Zeitwerk::Loader.for_gem` is a convenience shortcut for the common case in which a gem has its entry point directly under the `lib` directory:
|
261
|
+
|
262
|
+
```
|
263
|
+
lib/my_gem.rb # MyGem
|
264
|
+
lib/my_gem/version.rb # MyGem::VERSION
|
265
|
+
lib/my_gem/foo.rb # MyGem::Foo
|
266
|
+
```
|
267
|
+
|
268
|
+
Neither a gemspec nor a version file are technically required, this helper works as long as the code is organized using that standard structure.
|
269
|
+
|
270
|
+
If the entry point of your gem lives in a subdirectory of `lib` because it is reopening a namespace defined somewhere else, please use the generic API to setup the loader, and make sure you check the section [_Reopening third-party namespaces_](https://github.com/fxn/zeitwerk#reopening-third-party-namespaces) down below.
|
271
|
+
|
272
|
+
Conceptually, `for_gem` translates to:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
# lib/my_gem.rb
|
276
|
+
|
277
|
+
require "zeitwerk"
|
278
|
+
loader = Zeitwerk::Loader.new
|
279
|
+
loader.tag = File.basename(__FILE__, ".rb")
|
280
|
+
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
281
|
+
loader.push_dir(__dir__)
|
282
|
+
```
|
283
|
+
|
284
|
+
except that this method returns the same object in subsequent calls from the same file, in the unlikely case the gem wants to be able to reload.
|
285
|
+
|
286
|
+
If the main module references project constants at the top-level, Zeitwerk has to be ready to load them. Their definitions, in turn, may reference other project constants. And this is recursive. Therefore, it is important that the `setup` call happens above the main module definition:
|
206
287
|
|
207
288
|
```ruby
|
208
289
|
# lib/my_gem.rb (main file)
|
@@ -218,8 +299,6 @@ module MyGem
|
|
218
299
|
end
|
219
300
|
```
|
220
301
|
|
221
|
-
Zeitwerk works internally only with absolute paths to avoid costly file searches in `$LOAD_PATH`. Indeed, the root directories do not even need to belong to `$LOAD_PATH`, everything just works by design if they don't.
|
222
|
-
|
223
302
|
<a id="markdown-autoloading" name="autoloading"></a>
|
224
303
|
### Autoloading
|
225
304
|
|
@@ -424,6 +503,52 @@ class MyGem::Inflector < Zeitwerk::GemInflector
|
|
424
503
|
end
|
425
504
|
```
|
426
505
|
|
506
|
+
<a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
|
507
|
+
### The on_load callback
|
508
|
+
|
509
|
+
The usual place to run something when a file is loaded is the file itself. However, sometimes you'd like to be called, and this is possible with the `on_load` callback.
|
510
|
+
|
511
|
+
For example, let's imagine this class belongs to a Rails application:
|
512
|
+
|
513
|
+
```ruby
|
514
|
+
class SomeApiClient
|
515
|
+
class << self
|
516
|
+
attr_accessor :endpoint
|
517
|
+
end
|
518
|
+
end
|
519
|
+
```
|
520
|
+
|
521
|
+
With `on_load`, it is easy to schedule code at boot time that initializes `endpoint` according to the configuration:
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
# config/environments/development.rb
|
525
|
+
loader.on_load("SomeApiClient") do
|
526
|
+
SomeApiClient.endpoint = "https://api.dev"
|
527
|
+
end
|
528
|
+
|
529
|
+
# config/environments/production.rb
|
530
|
+
loader.on_load("SomeApiClient") do
|
531
|
+
SomeApiClient.endpoint = "https://api.prod"
|
532
|
+
end
|
533
|
+
```
|
534
|
+
|
535
|
+
Uses cases:
|
536
|
+
|
537
|
+
* Doing something with an autoloadable class or module in a Rails application during initialization, in a way that plays well with reloading. As in the previous example.
|
538
|
+
* Delaying the execution of the block until the class is loaded for performance.
|
539
|
+
* Delaying the execution of the block until the class is loaded because it follows the adapter pattern and better not to load the class if the user does not need it.
|
540
|
+
* Etc.
|
541
|
+
|
542
|
+
However, let me stress that the easiest way to accomplish that is to write whatever you have to do in the actual target file. `on_load` use cases are edgy, use it only if appropriate.
|
543
|
+
|
544
|
+
`on_load` receives the name of the target class or module as a string. The given block is executed every time its corresponding file is loaded. That includes reloads.
|
545
|
+
|
546
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
547
|
+
|
548
|
+
The block is executed once the loader has loaded the target. In particular, if the target was already loaded when the callback is defined, the block won't run. But if you reload and load the target again, then it will. Normally, you'll want to define `on_load` callbacks before `setup`.
|
549
|
+
|
550
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
551
|
+
|
427
552
|
<a id="markdown-logging" name="logging"></a>
|
428
553
|
### Logging
|
429
554
|
|
@@ -592,6 +717,42 @@ Trip = Struct.new { ... } # NOT SUPPORTED
|
|
592
717
|
|
593
718
|
This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
|
594
719
|
|
720
|
+
<a id="markdown-reopening-third-party-namespaces" name="reopening-third-party-namespaces"></a>
|
721
|
+
### Reopening third-party namespaces
|
722
|
+
|
723
|
+
Projects managed by Zeitwerk can work with namespaces defined by third-party libraries. However, they have to be loaded in memory before calling `setup`.
|
724
|
+
|
725
|
+
For example, let's imagine you're writing a gem that implements an adapter for [Active Job](https://guides.rubyonrails.org/active_job_basics.html) that uses AwesomeQueue as backend. By convention, your gem has to define a class called `ActiveJob::QueueAdapters::AwesomeQueue`, and it has to do so in a file with a matching path:
|
726
|
+
|
727
|
+
```ruby
|
728
|
+
# lib/active_job/queue_adapters/awesome_queue.rb
|
729
|
+
module ActiveJob
|
730
|
+
module QueueAdapters
|
731
|
+
class AwesomeQueue
|
732
|
+
# ...
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|
736
|
+
```
|
737
|
+
|
738
|
+
It is very important that your gem _reopens_ the modules `ActiveJob` and `ActiveJob::QueueAdapters` instead of _defining_ them. Because their proper definition lives in Active Job. Furthermore, if the project reloads, you do not want any of `ActiveJob` or `ActiveJob::QueueAdapters` to be reloaded.
|
739
|
+
|
740
|
+
Bottom line, Zeitwerk should not be managing those namespaces. Active Job owns them and defines them. Your gem needs to _reopen_ them.
|
741
|
+
|
742
|
+
In order to do so, you need to make sure those modules are loaded before calling `setup`. For instance, in the entry file for the gem:
|
743
|
+
|
744
|
+
```ruby
|
745
|
+
# Ensure these namespaces are reopened, not defined.
|
746
|
+
require "active_job"
|
747
|
+
require "active_job/queue_adapters"
|
748
|
+
|
749
|
+
require "zeitwerk"
|
750
|
+
loader = Zeitwerk::Loader.for_gem
|
751
|
+
loader.setup
|
752
|
+
```
|
753
|
+
|
754
|
+
With that, when Zeitwerk scans the file system and reaches the gem directories `lib/active_job` and `lib/active_job/queue_adapters`, it detects the corresponding modules already exist and therefore understands it does not have to manage them. The loader just descends into those directories. Eventually will reach `lib/active_job/queue_adapters/awesome_queue.rb`, and since `ActiveJob::QueueAdapters::AwesomeQueue` is unknown, Zeitwerk will manage it. Which is what happens regularly with the files in your gem. On reload, the namespaces are safe, won't be reloaded. The loader only reloads what it manages, which in this case is the adapter itself.
|
755
|
+
|
595
756
|
<a id="markdown-rules-of-thumb" name="rules-of-thumb"></a>
|
596
757
|
### Rules of thumb
|
597
758
|
|
@@ -607,14 +768,18 @@ This only affects explicit namespaces, those idioms work well for any other ordi
|
|
607
768
|
|
608
769
|
6. In a given process, ideally, there should be at most one loader with reloading enabled. Technically, you can have more, but it may get tricky if one refers to constants managed by the other one. Do that only if you know what you are doing.
|
609
770
|
|
610
|
-
<a id="markdown-
|
611
|
-
###
|
771
|
+
<a id="markdown-debuggers" name="debuggers"></a>
|
772
|
+
### Debuggers
|
773
|
+
|
774
|
+
<a id="markdown-break" name="break"></a>
|
775
|
+
#### Break
|
612
776
|
|
613
|
-
|
777
|
+
Zeitwerk works fine with [@gsamokovarov](https://github.com/gsamokovarov)'s [Break](https://github.com/gsamokovarov/break) debugger.
|
614
778
|
|
615
|
-
|
779
|
+
<a id="markdown-byebug" name="byebug"></a>
|
780
|
+
#### Byebug
|
616
781
|
|
617
|
-
|
782
|
+
Zeitwerk and [Byebug](https://github.com/deivid-rodriguez/byebug) are incompatible, classes or modules that belong to [explicit namespaces](#explicit-namespaces) are not autoloaded inside a Byebug session. See [this issue](https://github.com/deivid-rodriguez/byebug/issues/564#issuecomment-499413606) for further details.
|
618
783
|
|
619
784
|
<a id="markdown-pronunciation" name="pronunciation"></a>
|
620
785
|
## Pronunciation
|
@@ -14,24 +14,22 @@ module Zeitwerk
|
|
14
14
|
# the file system, to the loader responsible for them.
|
15
15
|
#
|
16
16
|
# @private
|
17
|
-
# @
|
17
|
+
# @sig Hash[String, Zeitwerk::Loader]
|
18
18
|
attr_reader :cpaths
|
19
19
|
|
20
20
|
# @private
|
21
|
-
# @
|
21
|
+
# @sig Mutex
|
22
22
|
attr_reader :mutex
|
23
23
|
|
24
24
|
# @private
|
25
|
-
# @
|
25
|
+
# @sig TracePoint
|
26
26
|
attr_reader :tracer
|
27
27
|
|
28
28
|
# Asserts `cpath` corresponds to an explicit namespace for which `loader`
|
29
29
|
# is responsible.
|
30
30
|
#
|
31
31
|
# @private
|
32
|
-
# @
|
33
|
-
# @param loader [Zeitwerk::Loader]
|
34
|
-
# @return [void]
|
32
|
+
# @sig (String, Zeitwerk::Loader) -> void
|
35
33
|
def register(cpath, loader)
|
36
34
|
mutex.synchronize do
|
37
35
|
cpaths[cpath] = loader
|
@@ -42,19 +40,22 @@ module Zeitwerk
|
|
42
40
|
end
|
43
41
|
|
44
42
|
# @private
|
45
|
-
# @
|
46
|
-
# @return [void]
|
43
|
+
# @sig (Zeitwerk::Loader) -> void
|
47
44
|
def unregister(loader)
|
48
45
|
cpaths.delete_if { |_cpath, l| l == loader }
|
49
46
|
disable_tracer_if_unneeded
|
50
47
|
end
|
51
48
|
|
49
|
+
private
|
50
|
+
|
51
|
+
# @sig () -> void
|
52
52
|
def disable_tracer_if_unneeded
|
53
53
|
mutex.synchronize do
|
54
54
|
tracer.disable if cpaths.empty?
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
# @sig (TracePoint) -> void
|
58
59
|
def tracepoint_class_callback(event)
|
59
60
|
# If the class is a singleton class, we won't do anything with it so we
|
60
61
|
# can bail out immediately. This is several orders of magnitude faster
|
@@ -2,16 +2,14 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
class GemInflector < Inflector
|
5
|
-
# @
|
5
|
+
# @sig (String) -> void
|
6
6
|
def initialize(root_file)
|
7
7
|
namespace = File.basename(root_file, ".rb")
|
8
8
|
lib_dir = File.dirname(root_file)
|
9
9
|
@version_file = File.join(lib_dir, namespace, "version.rb")
|
10
10
|
end
|
11
11
|
|
12
|
-
# @
|
13
|
-
# @param abspath [String]
|
14
|
-
# @return [String]
|
12
|
+
# @sig (String, String) -> String
|
15
13
|
def camelize(basename, abspath)
|
16
14
|
abspath == @version_file ? "VERSION" : super
|
17
15
|
end
|
data/lib/zeitwerk/inflector.rb
CHANGED
@@ -11,11 +11,9 @@ module Zeitwerk
|
|
11
11
|
#
|
12
12
|
# Takes into account hard-coded mappings configured with `inflect`.
|
13
13
|
#
|
14
|
-
# @
|
15
|
-
# @param _abspath [String]
|
16
|
-
# @return [String]
|
14
|
+
# @sig (String, String) -> String
|
17
15
|
def camelize(basename, _abspath)
|
18
|
-
overrides[basename] || basename.split('_').
|
16
|
+
overrides[basename] || basename.split('_').each(&:capitalize!).join
|
19
17
|
end
|
20
18
|
|
21
19
|
# Configures hard-coded inflections:
|
@@ -30,8 +28,7 @@ module Zeitwerk
|
|
30
28
|
# inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
|
31
29
|
# inflector.camelize("users_controller", abspath) # => "UsersController"
|
32
30
|
#
|
33
|
-
# @
|
34
|
-
# @return [void]
|
31
|
+
# @sig (Hash[String, String]) -> void
|
35
32
|
def inflect(inflections)
|
36
33
|
overrides.merge!(inflections)
|
37
34
|
end
|
@@ -41,7 +38,7 @@ module Zeitwerk
|
|
41
38
|
# Hard-coded basename to constant name user maps that override the default
|
42
39
|
# inflection logic.
|
43
40
|
#
|
44
|
-
# @
|
41
|
+
# @sig () -> Hash[String, String]
|
45
42
|
def overrides
|
46
43
|
@overrides ||= {}
|
47
44
|
end
|
data/lib/zeitwerk/kernel.rb
CHANGED
@@ -3,13 +3,23 @@
|
|
3
3
|
module Kernel
|
4
4
|
module_function
|
5
5
|
|
6
|
+
# We are going to decorate Kerner#require with two goals.
|
7
|
+
#
|
8
|
+
# First, by intercepting Kernel#require calls, we are able to autovivify
|
9
|
+
# modules on required directories, and also do internal housekeeping when
|
10
|
+
# managed files are loaded.
|
11
|
+
#
|
12
|
+
# On the other hand, if you publish a new version of a gem that is now managed
|
13
|
+
# by Zeitwerk, client code can reference directly your classes and modules and
|
14
|
+
# should not require anything. But if someone has legacy require calls around,
|
15
|
+
# they will work as expected, and in a compatible way.
|
16
|
+
#
|
6
17
|
# We cannot decorate with prepend + super because Kernel has already been
|
7
18
|
# included in Object, and changes in ancestors don't get propagated into
|
8
19
|
# already existing ancestor chains.
|
9
20
|
alias_method :zeitwerk_original_require, :require
|
10
21
|
|
11
|
-
# @
|
12
|
-
# @return [Boolean]
|
22
|
+
# @sig (String) -> true | false
|
13
23
|
def require(path)
|
14
24
|
if loader = Zeitwerk::Registry.loader_for(path)
|
15
25
|
if path.end_with?(".rb")
|
@@ -18,6 +28,7 @@ module Kernel
|
|
18
28
|
end
|
19
29
|
else
|
20
30
|
loader.on_dir_autoloaded(path)
|
31
|
+
true
|
21
32
|
end
|
22
33
|
else
|
23
34
|
zeitwerk_original_require(path).tap do |required|
|
@@ -30,4 +41,24 @@ module Kernel
|
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|
44
|
+
|
45
|
+
# By now, I have seen no way so far to decorate require_relative.
|
46
|
+
#
|
47
|
+
# For starters, at least in CRuby, require_relative does not delegate to
|
48
|
+
# require. Both require and require_relative delegate the bulk of their work
|
49
|
+
# to an internal C function called rb_require_safe. So, our require wrapper is
|
50
|
+
# not executed.
|
51
|
+
#
|
52
|
+
# On the other hand, we cannot use the aliasing technique above because
|
53
|
+
# require_relative receives a path relative to the directory of the file in
|
54
|
+
# which the call is performed. If a wrapper here invoked the original method,
|
55
|
+
# Ruby would resolve the relative path taking lib/zeitwerk as base directory.
|
56
|
+
#
|
57
|
+
# A workaround could be to extract the base directory from caller_locations,
|
58
|
+
# but what if someone else decorated require_relative before us? You can't
|
59
|
+
# really know with certainty where's the original call site in the stack.
|
60
|
+
#
|
61
|
+
# However, the main use case for require_relative is to load files from your
|
62
|
+
# own project. Projects managed by Zeitwerk don't do this for files managed by
|
63
|
+
# Zeitwerk, precisely.
|
33
64
|
end
|