zeitwerk 2.1.4 → 2.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ff8931860945782c104aae3e03599df5c84dfdfd05aa422b3b467f227fabb23
4
- data.tar.gz: aca07442b309c1d842ebcc9351c4c489c601331832308aee6f8f5bb49a43e4bc
3
+ metadata.gz: e89de8ad7897a95ccc14f71e1688eb826c7e01f2c0c9381e09de5dd8815d1974
4
+ data.tar.gz: 11bf19ab1b396399afc3110e8122cb783d2a2d64a889fa5ac7e506e65a8cc9be
5
5
  SHA512:
6
- metadata.gz: e42fb0efc142bae7da85f21d3d8650c5ef8a24f5f07b3b673ca6f4e74b952c90d7d935f4d2fd1c8efa217d0e19476472435df6ee7547f6c76f190796fc87c202
7
- data.tar.gz: 1ced31f03716433f32e534952c2811bee84885c6e376c5e5e227ce695bd891154fa8464747b4e0844a74dd1e9a2d196dd3cace12835312e340ae424834b58433
6
+ metadata.gz: b9a3e74bde3eec64186834b010f3c047d0b1c849a95c89747c6195b3a5fef974d4b1018cf4fca64a46e27aa5768c543d73b6203af80b170c20dab108544833e0
7
+ data.tar.gz: 2055af7e3b74fa7c6c3e0c1e7dc6bb1888736c5d8204106ed0504b83a3cba74f7db2a24a72a0e2afc995cf780d4ebcd5965c74262150bb9c90fc3abf4ae97310
data/README.md CHANGED
@@ -3,7 +3,7 @@
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/travis/com/fxn/zeitwerk.svg?style=for-the-badge)](https://travis-ci.com/fxn/zeitwerk)
6
+ [![Build Status](https://img.shields.io/travis/com/fxn/zeitwerk.svg?style=for-the-badge&branch=master)](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.
@@ -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
- require abspath unless shadowed_files.member?(abspath)
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
- log("constant #{cpath(*cref)} loaded from file #{file}") if logger
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.1.4"
4
+ VERSION = "2.1.5"
5
5
  end
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
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-22 00:00:00.000000000 Z
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