zeitwerk 2.1.4 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -1
- data/lib/zeitwerk/loader.rb +9 -24
- data/lib/zeitwerk/loader/callbacks.rb +6 -1
- data/lib/zeitwerk/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e89de8ad7897a95ccc14f71e1688eb826c7e01f2c0c9381e09de5dd8815d1974
|
|
4
|
+
data.tar.gz: 11bf19ab1b396399afc3110e8122cb783d2a2d64a889fa5ac7e506e65a8cc9be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9a3e74bde3eec64186834b010f3c047d0b1c849a95c89747c6195b3a5fef974d4b1018cf4fca64a46e27aa5768c543d73b6203af80b170c20dab108544833e0
|
|
7
|
+
data.tar.gz: 2055af7e3b74fa7c6c3e0c1e7dc6bb1888736c5d8204106ed0504b83a3cba74f7db2a24a72a0e2afc995cf780d4ebcd5965c74262150bb9c90fc3abf4ae97310
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
[](https://rubygems.org/gems/zeitwerk)
|
|
6
|
-
[](https://travis-ci.com/fxn/zeitwerk)
|
|
6
|
+
[](https://travis-ci.com/fxn/zeitwerk)
|
|
7
7
|
|
|
8
8
|
<!-- TOC -->
|
|
9
9
|
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
<!-- /TOC -->
|
|
39
39
|
|
|
40
|
+
<a id="markdown-introduction" name="introduction"></a>
|
|
40
41
|
## Introduction
|
|
41
42
|
|
|
42
43
|
Zeitwerk is an efficient and thread-safe code loader for Ruby.
|
|
@@ -51,6 +52,7 @@ Zeitwerk is also able to reload code, which may be handy for web applications. C
|
|
|
51
52
|
|
|
52
53
|
Finally, in some production setups it may be optimal to eager load all code upfront. Zeitwerk is able to do that too.
|
|
53
54
|
|
|
55
|
+
<a id="markdown-synopsis" name="synopsis"></a>
|
|
54
56
|
## Synopsis
|
|
55
57
|
|
|
56
58
|
Main interface for gems:
|
|
@@ -101,6 +103,7 @@ It is also possible to broadcast `eager_load` to all instances:
|
|
|
101
103
|
Zeitwerk::Loader.eager_load_all
|
|
102
104
|
```
|
|
103
105
|
|
|
106
|
+
<a id="markdown-file-structure" name="file-structure"></a>
|
|
104
107
|
## File structure
|
|
105
108
|
|
|
106
109
|
To have a file structure Zeitwerk can work with, just name files and directories after the name of the classes and modules they define:
|
|
@@ -126,6 +129,7 @@ app/models/user.rb -> User
|
|
|
126
129
|
app/controllers/admin/users_controller.rb -> Admin::UsersController
|
|
127
130
|
```
|
|
128
131
|
|
|
132
|
+
<a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
|
|
129
133
|
### Implicit namespaces
|
|
130
134
|
|
|
131
135
|
Directories without a matching Ruby file get modules autovivified automatically by Zeitwerk. For example, in
|
|
@@ -136,6 +140,7 @@ app/controllers/admin/users_controller.rb -> Admin::UsersController
|
|
|
136
140
|
|
|
137
141
|
`Admin` is autovivified as a module on demand, you do not need to define an `Admin` class or module in an `admin.rb` file explicitly.
|
|
138
142
|
|
|
143
|
+
<a id="markdown-explicit-namespaces" name="explicit-namespaces"></a>
|
|
139
144
|
### Explicit namespaces
|
|
140
145
|
|
|
141
146
|
Classes and modules that act as namespaces can also be explicitly defined, though. For instance, consider
|
|
@@ -158,6 +163,7 @@ end
|
|
|
158
163
|
|
|
159
164
|
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.
|
|
160
165
|
|
|
166
|
+
<a id="markdown-nested-root-directories" name="nested-root-directories"></a>
|
|
161
167
|
### Nested root directories
|
|
162
168
|
|
|
163
169
|
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.
|
|
@@ -170,8 +176,10 @@ app/models/concerns/geolocatable.rb
|
|
|
170
176
|
|
|
171
177
|
should define `Geolocatable`, not `Concerns::Geolocatable`.
|
|
172
178
|
|
|
179
|
+
<a id="markdown-usage" name="usage"></a>
|
|
173
180
|
## Usage
|
|
174
181
|
|
|
182
|
+
<a id="markdown-setup" name="setup"></a>
|
|
175
183
|
### Setup
|
|
176
184
|
|
|
177
185
|
Loaders are ready to load code right after calling `setup` on them:
|
|
@@ -194,6 +202,7 @@ The loader returned by `Zeitwerk::Loader.for_gem` has the directory of the calle
|
|
|
194
202
|
|
|
195
203
|
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.
|
|
196
204
|
|
|
205
|
+
<a id="markdown-reloading" name="reloading"></a>
|
|
197
206
|
### Reloading
|
|
198
207
|
|
|
199
208
|
Zeitwer is able to reload code, but you need to enable this feature:
|
|
@@ -221,6 +230,7 @@ In order for reloading to be thread-safe, you need to implement some coordinatio
|
|
|
221
230
|
|
|
222
231
|
On reloading, client code has to update anything that would otherwise be storing a stale object. For example, if the routing layer of a web framework stores controller class objects or instances in internal structures, on reload it has to refresh them somehow, possibly reevaluating routes.
|
|
223
232
|
|
|
233
|
+
<a id="markdown-eager-loading" name="eager-loading"></a>
|
|
224
234
|
### Eager loading
|
|
225
235
|
|
|
226
236
|
Zeitwerk instances are able to eager load their managed files:
|
|
@@ -250,6 +260,7 @@ This may be handy in top-level services, like web applications.
|
|
|
250
260
|
|
|
251
261
|
Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
|
|
252
262
|
|
|
263
|
+
<a id="markdown-preloading" name="preloading"></a>
|
|
253
264
|
### Preloading
|
|
254
265
|
|
|
255
266
|
Zeitwerk instances are able to preload files and directories.
|
|
@@ -265,10 +276,12 @@ This is a feature specifically thought for STIs in Rails, preloading the leafs o
|
|
|
265
276
|
|
|
266
277
|
The example above depicts several calls are supported, but `preload` accepts multiple arguments and arrays of strings as well.
|
|
267
278
|
|
|
279
|
+
<a id="markdown-inflection" name="inflection"></a>
|
|
268
280
|
### Inflection
|
|
269
281
|
|
|
270
282
|
Each individual loader needs an inflector to figure out which constant path would a given file or directory map to. Zeitwerk ships with two basic inflectors.
|
|
271
283
|
|
|
284
|
+
<a id="markdown-zeitwerkinflector" name="zeitwerkinflector"></a>
|
|
272
285
|
#### Zeitwerk::Inflector
|
|
273
286
|
|
|
274
287
|
This is a very basic inflector that converts snake case to camel case:
|
|
@@ -283,10 +296,12 @@ There are no inflection rules or global configuration that can affect this infle
|
|
|
283
296
|
|
|
284
297
|
This is the default inflector.
|
|
285
298
|
|
|
299
|
+
<a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
|
|
286
300
|
#### Zeitwerk::GemInflector
|
|
287
301
|
|
|
288
302
|
The loader instantiated behind the scenes by `Zeitwerk::Loader.for_gem` gets assigned by default an inflector that is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
|
|
289
303
|
|
|
304
|
+
<a id="markdown-custom-inflector" name="custom-inflector"></a>
|
|
290
305
|
#### Custom inflector
|
|
291
306
|
|
|
292
307
|
The inflectors that ship with Zeitwerk are deterministic and simple. But you can configure your own:
|
|
@@ -320,6 +335,7 @@ loader.inflector = MyInflector.new
|
|
|
320
335
|
|
|
321
336
|
This needs to be done before calling `setup`.
|
|
322
337
|
|
|
338
|
+
<a id="markdown-logging" name="logging"></a>
|
|
323
339
|
### Logging
|
|
324
340
|
|
|
325
341
|
Zeitwerk is silent by default, but you can configure a callable as logger:
|
|
@@ -348,6 +364,7 @@ If there is a logger configured, you'll see traces when autoloads are set, files
|
|
|
348
364
|
|
|
349
365
|
As a curiosity, if your project has namespaces you'll notice in the traces Zeitwerk sets autoloads for _directories_. That's a technique used to be able to descend into subdirectories on demand, avoiding that way unnecessary tree walks.
|
|
350
366
|
|
|
367
|
+
<a id="markdown-loader-tag" name="loader-tag"></a>
|
|
351
368
|
#### Loader tag
|
|
352
369
|
|
|
353
370
|
Loaders have a tag that is printed in traces in order to be able to distinguish them in globally logged activity:
|
|
@@ -368,6 +385,7 @@ The tag of a loader returned by `for_gem` is the basename of the root file witho
|
|
|
368
385
|
Zeitwerk@my_gem: constant MyGem::Foo loaded from ...
|
|
369
386
|
```
|
|
370
387
|
|
|
388
|
+
<a id="markdown-ignoring-parts-of-the-project" name="ignoring-parts-of-the-project"></a>
|
|
371
389
|
### Ignoring parts of the project
|
|
372
390
|
|
|
373
391
|
Zeitwerk ignores automatically any file or directory whose name starts with a dot, and any files that do not have extension ".rb".
|
|
@@ -378,6 +396,7 @@ You can ignore file names, directory names, and glob patterns. Glob patterns are
|
|
|
378
396
|
|
|
379
397
|
Let's see some use cases.
|
|
380
398
|
|
|
399
|
+
<a id="markdown-use-case-files-that-do-not-follow-the-conventions" name="use-case-files-that-do-not-follow-the-conventions"></a>
|
|
381
400
|
#### Use case: Files that do not follow the conventions
|
|
382
401
|
|
|
383
402
|
Let's suppose that your gem decorates something in `Kernel`:
|
|
@@ -406,6 +425,7 @@ loader.ignore(core_ext)
|
|
|
406
425
|
loader.setup
|
|
407
426
|
```
|
|
408
427
|
|
|
428
|
+
<a id="markdown-use-case-the-adapter-pattern" name="use-case-the-adapter-pattern"></a>
|
|
409
429
|
#### Use case: The adapter pattern
|
|
410
430
|
|
|
411
431
|
Another use case for ignoring files is the adapter pattern.
|
|
@@ -433,6 +453,7 @@ The chosen adapter, then, has to be loaded by hand somehow:
|
|
|
433
453
|
require "my_gem/db_adapters/#{config[:db_adapter]}"
|
|
434
454
|
```
|
|
435
455
|
|
|
456
|
+
<a id="markdown-use-case-test-files-mixed-with-implementation-files" name="use-case-test-files-mixed-with-implementation-files"></a>
|
|
436
457
|
#### Use case: Test files mixed with implementation files
|
|
437
458
|
|
|
438
459
|
There are project layouts that put implementation files and test files together. To ignore the test files, you can use a glob pattern like this:
|
|
@@ -443,6 +464,7 @@ loader.ignore(tests)
|
|
|
443
464
|
loader.setup
|
|
444
465
|
```
|
|
445
466
|
|
|
467
|
+
<a id="markdown-edge-cases" name="edge-cases"></a>
|
|
446
468
|
### Edge cases
|
|
447
469
|
|
|
448
470
|
A class or module that acts as a namespace:
|
|
@@ -471,20 +493,24 @@ Trip = Struct.new { ... } # NOT SUPPORTED
|
|
|
471
493
|
|
|
472
494
|
This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
|
|
473
495
|
|
|
496
|
+
<a id="markdown-pronunciation" name="pronunciation"></a>
|
|
474
497
|
## Pronunciation
|
|
475
498
|
|
|
476
499
|
"Zeitwerk" is pronounced [this way](http://share.hashref.com/zeitwerk/zeitwerk_pronunciation.mp3).
|
|
477
500
|
|
|
501
|
+
<a id="markdown-supported-ruby-versions" name="supported-ruby-versions"></a>
|
|
478
502
|
## Supported Ruby versions
|
|
479
503
|
|
|
480
504
|
Zeitwerk works with MRI 2.4.4 and above.
|
|
481
505
|
|
|
506
|
+
<a id="markdown-motivation" name="motivation"></a>
|
|
482
507
|
## Motivation
|
|
483
508
|
|
|
484
509
|
Since `require` has global side-effects, and there is no static way to verify that you have issued the `require` calls for code that your file depends on, in practice it is very easy to forget some. That introduces bugs that depend on the load order. Zeitwerk provides a way to forget about `require` in your own code, just name things following conventions and done.
|
|
485
510
|
|
|
486
511
|
On the other hand, autoloading in Rails is based on `const_missing`, which lacks fundamental information like the nesting and the resolution algorithm that was being used. Because of that, Rails autoloading is not able to match Ruby's semantics and that introduces a series of gotchas. The original goal of this project was to bring a better autoloading mechanism for Rails 6.
|
|
487
512
|
|
|
513
|
+
<a id="markdown-thanks" name="thanks"></a>
|
|
488
514
|
## Thanks
|
|
489
515
|
|
|
490
516
|
I'd like to thank [@matthewd](https://github.com/matthewd) for the discussions we've had about this topic in the past years, I learned a couple of tricks used in Zeitwerk from him.
|
|
@@ -493,6 +519,7 @@ Also, would like to thank [@Shopify](https://github.com/Shopify), [@rafaelfranca
|
|
|
493
519
|
|
|
494
520
|
Finally, many thanks to [@schurig](https://github.com/schurig) for recording an [audio file](http://share.hashref.com/zeitwerk/zeitwerk_pronunciation.mp3) with the pronunciation of "Zeitwerk" in perfect German. 💯
|
|
495
521
|
|
|
522
|
+
<a id="markdown-license" name="license"></a>
|
|
496
523
|
## License
|
|
497
524
|
|
|
498
525
|
Released under the MIT License, Copyright (c) 2019–<i>ω</i> Xavier Noria.
|
data/lib/zeitwerk/loader.rb
CHANGED
|
@@ -53,23 +53,6 @@ module Zeitwerk
|
|
|
53
53
|
# @return [Set<String>]
|
|
54
54
|
attr_reader :ignored_paths
|
|
55
55
|
|
|
56
|
-
# A _shadowed file_ is a file managed by this loader that is ignored when
|
|
57
|
-
# setting autoloads because its matching constant is taken. Either the
|
|
58
|
-
# constant is already defined, or there exists an autoload for it.
|
|
59
|
-
#
|
|
60
|
-
# Think $LOAD_PATH and require, only the first occurrence of a given
|
|
61
|
-
# relative name is loaded.
|
|
62
|
-
#
|
|
63
|
-
# This set keeps track of the absolute path of shadowed files, to be able to
|
|
64
|
-
# skip them while eager loading.
|
|
65
|
-
#
|
|
66
|
-
# Note this cannot be implemented with do_not_eager_load because these
|
|
67
|
-
# files are not autoloadable.
|
|
68
|
-
#
|
|
69
|
-
# @private
|
|
70
|
-
# @return [Set<String>]
|
|
71
|
-
attr_reader :shadowed_files
|
|
72
|
-
|
|
73
56
|
# Maps real absolute paths for which an autoload has been set ---and not
|
|
74
57
|
# executed--- to their corresponding parent class or module and constant
|
|
75
58
|
# name.
|
|
@@ -155,7 +138,6 @@ module Zeitwerk
|
|
|
155
138
|
@autoloaded_dirs = []
|
|
156
139
|
@to_unload = {}
|
|
157
140
|
@lazy_subdirs = {}
|
|
158
|
-
@shadowed_files = Set.new
|
|
159
141
|
@eager_load_exclusions = Set.new
|
|
160
142
|
|
|
161
143
|
# TODO: find a better name for these mutexes.
|
|
@@ -324,7 +306,6 @@ module Zeitwerk
|
|
|
324
306
|
autoloaded_dirs.clear
|
|
325
307
|
to_unload.clear
|
|
326
308
|
lazy_subdirs.clear
|
|
327
|
-
shadowed_files.clear
|
|
328
309
|
|
|
329
310
|
Registry.on_unload(self)
|
|
330
311
|
ExplicitNamespace.unregister(self)
|
|
@@ -362,19 +343,19 @@ module Zeitwerk
|
|
|
362
343
|
|
|
363
344
|
queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
|
|
364
345
|
while dir = queue.shift
|
|
346
|
+
const_get_if_autoload(dir)
|
|
347
|
+
|
|
365
348
|
each_abspath(dir) do |abspath|
|
|
366
349
|
next if eager_load_exclusions.member?(abspath)
|
|
367
350
|
|
|
368
351
|
if ruby?(abspath)
|
|
369
|
-
|
|
352
|
+
const_get_if_autoload(abspath)
|
|
370
353
|
elsif dir?(abspath)
|
|
371
354
|
queue << abspath
|
|
372
355
|
end
|
|
373
356
|
end
|
|
374
357
|
end
|
|
375
358
|
|
|
376
|
-
shadowed_files.clear
|
|
377
|
-
|
|
378
359
|
autoloaded_dirs.each do |autoloaded_dir|
|
|
379
360
|
Registry.unregister_autoload(autoloaded_dir)
|
|
380
361
|
end
|
|
@@ -516,7 +497,6 @@ module Zeitwerk
|
|
|
516
497
|
# First autoload for a Ruby file wins, just ignore subsequent ones.
|
|
517
498
|
if ruby?(autoload_path)
|
|
518
499
|
log("file #{file} is ignored because #{autoload_path} has precedence") if logger
|
|
519
|
-
shadowed_files.add(file)
|
|
520
500
|
else
|
|
521
501
|
promote_namespace_from_implicit_to_explicit(
|
|
522
502
|
dir: autoload_path,
|
|
@@ -527,7 +507,6 @@ module Zeitwerk
|
|
|
527
507
|
end
|
|
528
508
|
elsif cdef?(parent, cname)
|
|
529
509
|
log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
|
|
530
|
-
shadowed_files.add(file)
|
|
531
510
|
else
|
|
532
511
|
set_autoload(parent, cname, file)
|
|
533
512
|
end
|
|
@@ -685,6 +664,12 @@ module Zeitwerk
|
|
|
685
664
|
parent.const_defined?(cname, false)
|
|
686
665
|
end
|
|
687
666
|
|
|
667
|
+
def const_get_if_autoload(abspath)
|
|
668
|
+
if cref = autoloads[File.realpath(abspath)]
|
|
669
|
+
cref[0].const_get(cref[1], false)
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
688
673
|
def register_explicit_namespace(cpath)
|
|
689
674
|
ExplicitNamespace.register(cpath, self)
|
|
690
675
|
end
|
|
@@ -8,7 +8,12 @@ module Zeitwerk::Loader::Callbacks
|
|
|
8
8
|
cref = autoloads.delete(file)
|
|
9
9
|
to_unload[cpath(*cref)] = [file, cref] if reloading_enabled?
|
|
10
10
|
Zeitwerk::Registry.unregister_autoload(file)
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
if logger && cdef?(*cref)
|
|
13
|
+
log("constant #{cpath(*cref)} loaded from file #{file}")
|
|
14
|
+
elsif !cdef?(*cref)
|
|
15
|
+
raise NameError, "expected file #{file} to define constant #{cpath(*cref)}, but didn't"
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
# Invoked from our decorated Kernel#require when a managed directory is
|
data/lib/zeitwerk/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zeitwerk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Xavier Noria
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-04-
|
|
11
|
+
date: 2019-04-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |2
|
|
14
14
|
Zeitwerk implements constant autoloading with Ruby semantics. Each gem
|