zeitwerk 2.3.1 → 2.5.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +213 -47
- data/lib/zeitwerk.rb +2 -0
- data/lib/zeitwerk/autoloads.rb +69 -0
- data/lib/zeitwerk/explicit_namespace.rb +17 -10
- data/lib/zeitwerk/gem_inflector.rb +2 -4
- data/lib/zeitwerk/inflector.rb +4 -7
- data/lib/zeitwerk/kernel.rb +7 -6
- data/lib/zeitwerk/loader.rb +112 -453
- data/lib/zeitwerk/loader/callbacks.rb +28 -12
- data/lib/zeitwerk/loader/config.rb +308 -0
- data/lib/zeitwerk/loader/helpers.rb +95 -0
- data/lib/zeitwerk/real_mod_name.rb +1 -2
- data/lib/zeitwerk/registry.rb +19 -30
- data/lib/zeitwerk/version.rb +1 -1
- metadata +12 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ac5e0ce2eba71a2099ead24de8762e3e737656ff4bcae57c83bb5797e922f72
|
4
|
+
data.tar.gz: 85e7c762a5164301ba5bcce82a31e6c49bc7c6caff2b4abff23c425f10374c69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f258acabd660f54702eb904550a109b08bdacff2cecc92d610376376f77ccf70ed421e09c64144930d2a145ebbedb7e9c3c26ff21ad2c1b40dd0537d9947b4d4
|
7
|
+
data.tar.gz: 1612dc2d505fe37b748d81a9055e36a91f9f73267ac273b39a4cbea3736cdd1baa4c557e3059d8f3a9a9807d450e29f3d75c7cb18c94c37ded70768444313336
|
data/README.md
CHANGED
@@ -3,40 +3,48 @@
|
|
3
3
|
|
4
4
|
|
5
5
|
[![Gem Version](https://img.shields.io/gem/v/zeitwerk.svg?style=for-the-badge)](https://rubygems.org/gems/zeitwerk)
|
6
|
-
[![Build Status](https://img.shields.io/
|
6
|
+
[![Build Status](https://img.shields.io/github/workflow/status/fxn/zeitwerk/CI?event=push&style=for-the-badge)](https://github.com/fxn/zeitwerk/actions?query=event%3Apush)
|
7
7
|
|
8
8
|
<!-- TOC -->
|
9
9
|
|
10
10
|
- [Introduction](#introduction)
|
11
11
|
- [Synopsis](#synopsis)
|
12
12
|
- [File structure](#file-structure)
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
- [The idea: File paths match constant paths](#the-idea-file-paths-match-constant-paths)
|
14
|
+
- [Inner simple constants](#inner-simple-constants)
|
15
|
+
- [Root directories and root namespaces](#root-directories-and-root-namespaces)
|
16
|
+
- [The default root namespace is `Object`](#the-default-root-namespace-is-object)
|
17
|
+
- [Custom root namespaces](#custom-root-namespaces)
|
16
18
|
- [Nested root directories](#nested-root-directories)
|
19
|
+
- [Implicit namespaces](#implicit-namespaces)
|
20
|
+
- [Explicit namespaces](#explicit-namespaces)
|
21
|
+
- [Collapsing directories](#collapsing-directories)
|
17
22
|
- [Usage](#usage)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
- [
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- [
|
35
|
-
- [
|
36
|
-
- [
|
37
|
-
|
38
|
-
|
39
|
-
|
23
|
+
- [Setup](#setup)
|
24
|
+
- [Generic](#generic)
|
25
|
+
- [for_gem](#for_gem)
|
26
|
+
- [Autoloading](#autoloading)
|
27
|
+
- [Eager loading](#eager-loading)
|
28
|
+
- [Reloading](#reloading)
|
29
|
+
- [Inflection](#inflection)
|
30
|
+
- [Zeitwerk::Inflector](#zeitwerkinflector)
|
31
|
+
- [Zeitwerk::GemInflector](#zeitwerkgeminflector)
|
32
|
+
- [Custom inflector](#custom-inflector)
|
33
|
+
- [The on_load callback](#the-on_load-callback)
|
34
|
+
- [The on_unload callback](#the-on_unload-callback)
|
35
|
+
- [Technical details](#technical-details)
|
36
|
+
- [Logging](#logging)
|
37
|
+
- [Loader tag](#loader-tag)
|
38
|
+
- [Ignoring parts of the project](#ignoring-parts-of-the-project)
|
39
|
+
- [Use case: Files that do not follow the conventions](#use-case-files-that-do-not-follow-the-conventions)
|
40
|
+
- [Use case: The adapter pattern](#use-case-the-adapter-pattern)
|
41
|
+
- [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
|
42
|
+
- [Edge cases](#edge-cases)
|
43
|
+
- [Reopening third-party namespaces](#reopening-third-party-namespaces)
|
44
|
+
- [Rules of thumb](#rules-of-thumb)
|
45
|
+
- [Debuggers](#debuggers)
|
46
|
+
- [Break](#break)
|
47
|
+
- [Byebug](#byebug)
|
40
48
|
- [Pronunciation](#pronunciation)
|
41
49
|
- [Supported Ruby versions](#supported-ruby-versions)
|
42
50
|
- [Testing](#testing)
|
@@ -116,6 +124,9 @@ Zeitwerk::Loader.eager_load_all
|
|
116
124
|
<a id="markdown-file-structure" name="file-structure"></a>
|
117
125
|
## File structure
|
118
126
|
|
127
|
+
<a id="markdown-the-idea-file-paths-match-constant-paths" name="the-idea-file-paths-match-constant-paths"></a>
|
128
|
+
### The idea: File paths match constant paths
|
129
|
+
|
119
130
|
To have a file structure Zeitwerk can work with, just name files and directories after the name of the classes and modules they define:
|
120
131
|
|
121
132
|
```
|
@@ -125,20 +136,85 @@ lib/my_gem/bar_baz.rb -> MyGem::BarBaz
|
|
125
136
|
lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
|
126
137
|
```
|
127
138
|
|
128
|
-
|
139
|
+
You can tune that a bit by [collapsing directories](#collapsing-directories), or by [ignoring parts of the project](#ignoring-parts-of-the-project), but that is the main idea.
|
140
|
+
|
141
|
+
<a id="markdown-inner-simple-constants" name="inner-simple-constants"></a>
|
142
|
+
### Inner simple constants
|
143
|
+
|
144
|
+
While a simple constant like `HttpCrawler::MAX_RETRIES` can be defined in its own file:
|
129
145
|
|
130
146
|
```ruby
|
131
|
-
|
132
|
-
|
147
|
+
# http_crawler/max_retries.rb
|
148
|
+
HttpCrawler::MAX_RETRIES = 10
|
133
149
|
```
|
134
150
|
|
135
|
-
|
151
|
+
that is not required, you can also define it the regular way:
|
136
152
|
|
153
|
+
```ruby
|
154
|
+
# http_crawler.rb
|
155
|
+
class HttpCrawler
|
156
|
+
MAX_RETRIES = 10
|
157
|
+
end
|
137
158
|
```
|
138
|
-
|
139
|
-
|
159
|
+
|
160
|
+
<a id="markdown-root-directories-and-root-namespaces" name="root-directories-and-root-namespaces"></a>
|
161
|
+
### Root directories and root namespaces
|
162
|
+
|
163
|
+
Every directory configured with `push_dir` is called a _root directory_, and they represent _root namespaces_.
|
164
|
+
|
165
|
+
<a id="markdown-the-default-root-namespace-is-object" name="the-default-root-namespace-is-object"></a>
|
166
|
+
#### The default root namespace is `Object`
|
167
|
+
|
168
|
+
By default, the namespace associated to a root directory is the top-level one: `Object`.
|
169
|
+
|
170
|
+
For example, given
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
loader.push_dir("#{__dir__}/models")
|
174
|
+
loader.push_dir("#{__dir__}/serializers"))
|
175
|
+
```
|
176
|
+
|
177
|
+
these are the expected classes and modules being defined by these files:
|
178
|
+
|
179
|
+
```
|
180
|
+
models/user.rb -> User
|
181
|
+
serializers/user_serializer.rb -> UserSerializer
|
182
|
+
```
|
183
|
+
|
184
|
+
<a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
|
185
|
+
#### Custom root namespaces
|
186
|
+
|
187
|
+
While `Object` is by far the most common root namespace, you can associate a different one to a particular root directory. The method `push_dir` accepts a class or module object in the optional `namespace` keyword argument.
|
188
|
+
|
189
|
+
For example, given:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
require "active_job"
|
193
|
+
require "active_job/queue_adapters"
|
194
|
+
loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
|
140
195
|
```
|
141
196
|
|
197
|
+
a file defining `ActiveJob::QueueAdapters::MyQueueAdapter` does not need the conventional parent directories, you can (and have to) store the file directly below `adapters`:
|
198
|
+
|
199
|
+
```
|
200
|
+
adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapter
|
201
|
+
```
|
202
|
+
|
203
|
+
Please, note that the given root 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::Deliveries`, that one can be reloaded.
|
204
|
+
|
205
|
+
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
206
|
+
#### Nested root directories
|
207
|
+
|
208
|
+
Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
|
209
|
+
|
210
|
+
Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
|
211
|
+
|
212
|
+
```
|
213
|
+
app/models/concerns/geolocatable.rb
|
214
|
+
```
|
215
|
+
|
216
|
+
should define `Geolocatable`, not `Concerns::Geolocatable`.
|
217
|
+
|
142
218
|
<a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
|
143
219
|
### Implicit namespaces
|
144
220
|
|
@@ -186,7 +262,7 @@ booking/actions/create.rb -> Booking::Create
|
|
186
262
|
To make it work that way, configure Zeitwerk to collapse said directory:
|
187
263
|
|
188
264
|
```ruby
|
189
|
-
loader.collapse("booking/actions")
|
265
|
+
loader.collapse("#{__dir__}/booking/actions")
|
190
266
|
```
|
191
267
|
|
192
268
|
This method accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
|
@@ -196,22 +272,9 @@ You can pass directories and glob patterns. Glob patterns are expanded when they
|
|
196
272
|
To illustrate usage of glob patterns, if `actions` in the example above is part of a standardized structure, you could use a wildcard:
|
197
273
|
|
198
274
|
```ruby
|
199
|
-
loader.collapse("
|
275
|
+
loader.collapse("#{__dir__}/*/actions")
|
200
276
|
```
|
201
277
|
|
202
|
-
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
203
|
-
### Nested root directories
|
204
|
-
|
205
|
-
Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
|
206
|
-
|
207
|
-
Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
|
208
|
-
|
209
|
-
```
|
210
|
-
app/models/concerns/geolocatable.rb
|
211
|
-
```
|
212
|
-
|
213
|
-
should define `Geolocatable`, not `Concerns::Geolocatable`.
|
214
|
-
|
215
278
|
<a id="markdown-usage" name="usage"></a>
|
216
279
|
## Usage
|
217
280
|
|
@@ -486,6 +549,109 @@ class MyGem::Inflector < Zeitwerk::GemInflector
|
|
486
549
|
end
|
487
550
|
```
|
488
551
|
|
552
|
+
<a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
|
553
|
+
### The on_load callback
|
554
|
+
|
555
|
+
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.
|
556
|
+
|
557
|
+
For example, let's imagine this class belongs to a Rails application:
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
class SomeApiClient
|
561
|
+
class << self
|
562
|
+
attr_accessor :endpoint
|
563
|
+
end
|
564
|
+
end
|
565
|
+
```
|
566
|
+
|
567
|
+
With `on_load`, it is easy to schedule code at boot time that initializes `endpoint` according to the configuration:
|
568
|
+
|
569
|
+
```ruby
|
570
|
+
# config/environments/development.rb
|
571
|
+
loader.on_load("SomeApiClient") do |klass, _abspath|
|
572
|
+
klass.endpoint = "https://api.dev"
|
573
|
+
end
|
574
|
+
|
575
|
+
# config/environments/production.rb
|
576
|
+
loader.on_load("SomeApiClient") do |klass, _abspath|
|
577
|
+
klass.endpoint = "https://api.prod"
|
578
|
+
end
|
579
|
+
```
|
580
|
+
|
581
|
+
Some uses cases:
|
582
|
+
|
583
|
+
* 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.
|
584
|
+
* Delaying the execution of the block until the class is loaded for performance.
|
585
|
+
* 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.
|
586
|
+
|
587
|
+
`on_load` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is loaded. That includes reloads.
|
588
|
+
|
589
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
590
|
+
|
591
|
+
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`.
|
592
|
+
|
593
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
594
|
+
|
595
|
+
It is also possible to be called when any constant managed by the loader is loaded:
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
loader.on_load do |cpath, value, abspath|
|
599
|
+
# ...
|
600
|
+
end
|
601
|
+
```
|
602
|
+
|
603
|
+
The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
|
604
|
+
|
605
|
+
Multiple callbacks like these are supported, and they run in order of definition.
|
606
|
+
|
607
|
+
There are use cases for this last catch-all callback, but they are rare. If you just need to understand how things are being loaded for debugging purposes, please remember that `Zeitwerk::Loader#log!` logs plenty of information.
|
608
|
+
|
609
|
+
If both types of callbacks are defined, the specific ones run first.
|
610
|
+
|
611
|
+
<a id="markdown-the-on_unload-callback" name="the-on_unload-callback"></a>
|
612
|
+
### The on_unload callback
|
613
|
+
|
614
|
+
When reloading is enabled, you may occasionally need to execute something before a certain autoloaded class or module is unloaded. The `on_unload` callback allows you to do that.
|
615
|
+
|
616
|
+
For example, let's imagine that a `Country` class fetches a list of countries and caches them when it is loaded. You might want to clear that cache if unloaded:
|
617
|
+
|
618
|
+
```ruby
|
619
|
+
loader.on_unload("Country") do |klass, _abspath|
|
620
|
+
klass.clear_cache
|
621
|
+
end
|
622
|
+
```
|
623
|
+
|
624
|
+
`on_unload` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is unloaded.
|
625
|
+
|
626
|
+
`on_unload` blocks are executed before the class is unloaded, but in the middle of unloading, which happens in an unspecified order. Therefore, **that callback should not refer to any reloadable constant because there is no guarantee the constant works there**. Those blocks should rely on objects only, as in the example above, or regular constants not managed by the loader. This remark is transitive, applies to any methods invoked within the block.
|
627
|
+
|
628
|
+
Multiple callbacks on the same target are supported, and they run in order of definition.
|
629
|
+
|
630
|
+
Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
|
631
|
+
|
632
|
+
It is also possible to be called when any constant managed by the loader is unloaded:
|
633
|
+
|
634
|
+
```ruby
|
635
|
+
loader.on_unload do |cpath, value, abspath|
|
636
|
+
# ...
|
637
|
+
end
|
638
|
+
```
|
639
|
+
|
640
|
+
The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
|
641
|
+
|
642
|
+
Multiple callbacks like these are supported, and they run in order of definition.
|
643
|
+
|
644
|
+
If both types of callbacks are defined, the specific ones run first.
|
645
|
+
|
646
|
+
<a id="markdown-technical-details" name="technical-details"></a>
|
647
|
+
#### Technical details
|
648
|
+
|
649
|
+
Zeitwerk uses the word "unload" to ease communication and for symmetry with `on_load`. However, in Ruby you cannot unload things for real. So, when does `on_unload` technically happen?
|
650
|
+
|
651
|
+
When unloading, Zeitwerk issues `Module#remove_const` calls. Classes and modules are no longer reachable through their constants, and `on_unload` callbacks are executed right before those calls.
|
652
|
+
|
653
|
+
Technically, though, the objects themselves are still alive, but if everything is used as expected and they are not stored in any non-reloadable place (don't do that), they are ready for garbage collection, which is when the real unloading happens.
|
654
|
+
|
489
655
|
<a id="markdown-logging" name="logging"></a>
|
490
656
|
### Logging
|
491
657
|
|
data/lib/zeitwerk.rb
CHANGED
@@ -3,10 +3,12 @@
|
|
3
3
|
module Zeitwerk
|
4
4
|
require_relative "zeitwerk/real_mod_name"
|
5
5
|
require_relative "zeitwerk/loader"
|
6
|
+
require_relative "zeitwerk/autoloads"
|
6
7
|
require_relative "zeitwerk/registry"
|
7
8
|
require_relative "zeitwerk/explicit_namespace"
|
8
9
|
require_relative "zeitwerk/inflector"
|
9
10
|
require_relative "zeitwerk/gem_inflector"
|
10
11
|
require_relative "zeitwerk/kernel"
|
11
12
|
require_relative "zeitwerk/error"
|
13
|
+
require_relative "zeitwerk/version"
|
12
14
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Zeitwerk
|
2
|
+
# @private
|
3
|
+
class Autoloads
|
4
|
+
# Maps crefs for which an autoload has been defined to the corresponding
|
5
|
+
# absolute path.
|
6
|
+
#
|
7
|
+
# [Object, :User] => "/Users/fxn/blog/app/models/user.rb"
|
8
|
+
# [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
|
9
|
+
# ...
|
10
|
+
#
|
11
|
+
# This colection is transient, callbacks delete its entries as autoloads get
|
12
|
+
# executed.
|
13
|
+
#
|
14
|
+
# @sig Hash[[Module, Symbol], String]
|
15
|
+
attr_reader :c2a
|
16
|
+
|
17
|
+
# This is the inverse of c2a, for inverse lookups.
|
18
|
+
#
|
19
|
+
# @sig Hash[String, [Module, Symbol]]
|
20
|
+
attr_reader :a2c
|
21
|
+
|
22
|
+
# @sig () -> void
|
23
|
+
def initialize
|
24
|
+
@c2a = {}
|
25
|
+
@a2c = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# @sig (Module, Symbol, String) -> void
|
29
|
+
def define(parent, cname, abspath)
|
30
|
+
parent.autoload(cname, abspath)
|
31
|
+
cref = [parent, cname]
|
32
|
+
c2a[cref] = abspath
|
33
|
+
a2c[abspath] = cref
|
34
|
+
end
|
35
|
+
|
36
|
+
# @sig () { () -> [[Module, Symbol], String] } -> void
|
37
|
+
def each(&block)
|
38
|
+
c2a.each(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @sig (Module, Symbol) -> String?
|
42
|
+
def abspath_for(parent, cname)
|
43
|
+
c2a[[parent, cname]]
|
44
|
+
end
|
45
|
+
|
46
|
+
# @sig (String) -> [Module, Symbol]?
|
47
|
+
def cref_for(abspath)
|
48
|
+
a2c[abspath]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @sig (String) -> [Module, Symbol]?
|
52
|
+
def delete(abspath)
|
53
|
+
cref = a2c.delete(abspath)
|
54
|
+
c2a.delete(cref)
|
55
|
+
cref
|
56
|
+
end
|
57
|
+
|
58
|
+
# @sig () -> void
|
59
|
+
def clear
|
60
|
+
c2a.clear
|
61
|
+
a2c.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
# @sig () -> bool
|
65
|
+
def empty?
|
66
|
+
c2a.empty? && a2c.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -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,27 +40,36 @@ 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
|
61
62
|
# than accessing its name.
|
62
63
|
return if event.self.singleton_class?
|
63
64
|
|
64
|
-
#
|
65
|
-
#
|
65
|
+
# It might be tempting to return if name.nil?, to avoid the computation
|
66
|
+
# of a hash code and delete call. But Ruby does not trigger the :class
|
67
|
+
# event on Class.new or Module.new, so that would incur in an extra call
|
68
|
+
# for nothing.
|
69
|
+
#
|
70
|
+
# On the other hand, if we were called, cpaths is not empty. Otherwise
|
71
|
+
# the tracer is disabled. So we do need to go ahead with the hash code
|
72
|
+
# computation and delete call.
|
66
73
|
if loader = cpaths.delete(real_mod_name(event.self))
|
67
74
|
loader.on_namespace_loaded(event.self)
|
68
75
|
disable_tracer_if_unneeded
|