zeitwerk 2.1.5 → 2.1.10

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: e89de8ad7897a95ccc14f71e1688eb826c7e01f2c0c9381e09de5dd8815d1974
4
- data.tar.gz: 11bf19ab1b396399afc3110e8122cb783d2a2d64a889fa5ac7e506e65a8cc9be
3
+ metadata.gz: 3dbf7502d4126651ab645926f072af5f5164fcb495c06ae32e9748aa2807670e
4
+ data.tar.gz: e124bca82ed054d1ca304e95605ad011a2c6411792653c3c86b785a3cc956e06
5
5
  SHA512:
6
- metadata.gz: b9a3e74bde3eec64186834b010f3c047d0b1c849a95c89747c6195b3a5fef974d4b1018cf4fca64a46e27aa5768c543d73b6203af80b170c20dab108544833e0
7
- data.tar.gz: 2055af7e3b74fa7c6c3e0c1e7dc6bb1888736c5d8204106ed0504b83a3cba74f7db2a24a72a0e2afc995cf780d4ebcd5965c74262150bb9c90fc3abf4ae97310
6
+ metadata.gz: 7efe14aeb7e16563d6a1b69b2aac82d75dd1c4e79dae2fb4654e7c93742e15018f16fad4591f8763bf03b2f9d9d71a6e95209907a0765952dafa8dedc957d2ef
7
+ data.tar.gz: 8dea5ad3b1979cacb619b13385b337e81cc08efc39e0411f890229f0a9d17035fdfb17cf2ac84f6dcd3eeec4b4daa732e796e51fc1fdf707c210d9943481edcf
data/README.md CHANGED
@@ -17,7 +17,6 @@
17
17
  - [Setup](#setup)
18
18
  - [Reloading](#reloading)
19
19
  - [Eager loading](#eager-loading)
20
- - [Preloading](#preloading)
21
20
  - [Inflection](#inflection)
22
21
  - [Zeitwerk::Inflector](#zeitwerkinflector)
23
22
  - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
@@ -29,6 +28,7 @@
29
28
  - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
30
29
  - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
31
30
  - [Edge cases](#edge-cases)
31
+ - [Rules of thumb](#rules-of-thumb)
32
32
  - [Pronunciation](#pronunciation)
33
33
  - [Supported Ruby versions](#supported-ruby-versions)
34
34
  - [Motivation](#motivation)
@@ -42,15 +42,13 @@
42
42
 
43
43
  Zeitwerk is an efficient and thread-safe code loader for Ruby.
44
44
 
45
- Given a conventional file structure, Zeitwerk loads your project's classes and modules on demand. You don't need to write `require` calls for your own files, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby's semantics for constants.
45
+ Given a [conventional file structure](#file-structure), Zeitwerk is able to load your project's classes and modules on demand (autoloading), or upfront (eager loading). You don't need to write `require` calls for your own files, rather, you can streamline your programming knowing that your classes and modules are available everywhere. This feature is efficient, thread-safe, and matches Ruby's semantics for constants.
46
46
 
47
- 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`. Furthermore, by design, Zeitwerk does only one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
47
+ Zeitwerk is also able to reload code, which may be handy while developing web applications. Coordination is needed to reload in a thread-safe manner. The documentation below explains how to do this.
48
48
 
49
- The library is designed so that each gem and application can have their own loader, independent of each other. Each loader has its own configuration, inflector, and optional logger.
49
+ 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.
50
50
 
51
- Zeitwerk is also able to reload code, which may be handy for web applications. Coordination is needed to reload in a thread-safe manner. The documentation below explains how to do this.
52
-
53
- Finally, in some production setups it may be optimal to eager load all code upfront. Zeitwerk is able to do that too.
51
+ 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`. Furthermore, Zeitwerk does only one single scan of the project tree, and it descends into subdirectories lazily, only if their namespaces are used.
54
52
 
55
53
  <a id="markdown-synopsis" name="synopsis"></a>
56
54
  ## Synopsis
@@ -63,11 +61,12 @@ Main interface for gems:
63
61
  require "zeitwerk"
64
62
  loader = Zeitwerk::Loader.for_gem
65
63
  loader.setup # ready!
66
- # loader.eager_load, optionally
67
64
 
68
65
  module MyGem
69
66
  # ...
70
67
  end
68
+
69
+ loader.eager_load # optionally
71
70
  ```
72
71
 
73
72
  Main generic interface:
@@ -205,7 +204,7 @@ Zeitwerk works internally only with absolute paths to avoid costly file searches
205
204
  <a id="markdown-reloading" name="reloading"></a>
206
205
  ### Reloading
207
206
 
208
- Zeitwer is able to reload code, but you need to enable this feature:
207
+ Zeitwerk is able to reload code, but you need to enable this feature:
209
208
 
210
209
  ```ruby
211
210
  loader = Zeitwerk::Loader.new
@@ -222,7 +221,7 @@ Enabling reloading after setup raises `Zeitwerk::Error`. Similarly, calling `rel
222
221
 
223
222
  Generally speaking, reloading is useful while developing running services like web applications. Gems that implement regular libraries, so to speak, or services running in testing or production environments, won't normally have a use case for reloading. If reloading is not enabled, Zeitwerk is able to use less memory.
224
223
 
225
- Reloading removes the currently loaded classes and modules, resets the loader so that it will pick whatever is in the file system now, and runs preloads if there are any.
224
+ Reloading removes the currently loaded classes and modules and resets the loader so that it will pick whatever is in the file system now.
226
225
 
227
226
  It is important to highlight that this is an instance method. Don't worry about project dependencies managed by Zeitwerk, their loaders are independent.
228
227
 
@@ -260,22 +259,6 @@ This may be handy in top-level services, like web applications.
260
259
 
261
260
  Note that thanks to idempotence `Zeitwerk::Loader.eager_load_all` won't eager load twice if any of the instances already eager loaded.
262
261
 
263
- <a id="markdown-preloading" name="preloading"></a>
264
- ### Preloading
265
-
266
- Zeitwerk instances are able to preload files and directories.
267
-
268
- ```ruby
269
- loader.preload("app/models/videogame.rb")
270
- loader.preload("app/models/book.rb")
271
- ```
272
-
273
- The call can happen before `setup` (preloads during setup), or after `setup` (preloads on the spot). Each reload preloads too.
274
-
275
- This is a feature specifically thought for STIs in Rails, preloading the leafs of a STI tree ensures all classes are known when doing a query.
276
-
277
- The example above depicts several calls are supported, but `preload` accepts multiple arguments and arrays of strings as well.
278
-
279
262
  <a id="markdown-inflection" name="inflection"></a>
280
263
  ### Inflection
281
264
 
@@ -338,7 +321,15 @@ This needs to be done before calling `setup`.
338
321
  <a id="markdown-logging" name="logging"></a>
339
322
  ### Logging
340
323
 
341
- Zeitwerk is silent by default, but you can configure a callable as logger:
324
+ Zeitwerk is silent by default, but you can ask loaders to trace their activity. Logging is meant just for troubleshooting, shouldn't normally be enabled.
325
+
326
+ The `log!` method is a quick shortcut to let the loader log to `$stdout`:
327
+
328
+ ```
329
+ loader.log!
330
+ ```
331
+
332
+ If you want more control, a logger can be configured as a callable
342
333
 
343
334
  ```ruby
344
335
  loader.logger = method(:puts)
@@ -392,7 +383,7 @@ Zeitwerk ignores automatically any file or directory whose name starts with a do
392
383
 
393
384
  However, sometimes it might still be convenient to tell Zeitwerk to completely ignore some particular Ruby file or directory. That is possible with `ignore`, which accepts an arbitrary number of strings or `Pathname` objects, and also an array of them.
394
385
 
395
- You can ignore file names, directory names, and glob patterns. Glob patterns are expanded on setup and on reload.
386
+ You can ignore file names, directory names, and glob patterns. Glob patterns are expanded when they are added and again on each reload.
396
387
 
397
388
  Let's see some use cases.
398
389
 
@@ -453,6 +444,8 @@ The chosen adapter, then, has to be loaded by hand somehow:
453
444
  require "my_gem/db_adapters/#{config[:db_adapter]}"
454
445
  ```
455
446
 
447
+ Note that since the directory is ignored, the required adapter can instantiate another loader to manage its subtree, if desired. Such loader would coexist with the main one just fine.
448
+
456
449
  <a id="markdown-use-case-test-files-mixed-with-implementation-files" name="use-case-test-files-mixed-with-implementation-files"></a>
457
450
  #### Use case: Test files mixed with implementation files
458
451
 
@@ -493,6 +486,21 @@ Trip = Struct.new { ... } # NOT SUPPORTED
493
486
 
494
487
  This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
495
488
 
489
+ <a id="markdown-rules-of-thumb" name="rules-of-thumb"></a>
490
+ ### Rules of thumb
491
+
492
+ 1. Different loaders should manage different directory trees. It is an error condition to configure overlapping root directories in different loaders.
493
+
494
+ 2. Think the mere existence of a file is effectively like writing a `require` call for them, which is executed on demand (autoload) or upfront (eager load).
495
+
496
+ 3. In that line, if two loaders manage files that translate to the same constant in the same namespace, the first one wins, the rest are ignored. Similar to what happens with `require` and `$LOAD_PATH`, only the first occurrence matters.
497
+
498
+ 4. Projects that reopen a namespace defined by some dependency have to ensure said namespace is loaded before setup. That is, the project has to make sure it reopens, rather than define. This is often accomplished just loading the dependency.
499
+
500
+ 5. Objects stored in reloadable constants should not be cached in places that are not reloaded. For example, non-reloadable classes should not subclass a reloadable class, or mixin a reloadable module. Otherwise, after reloading, those classes or module objects would become stale. Referring to constants in dynamic places like method calls or lambdas is fine.
501
+
502
+ 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.
503
+
496
504
  <a id="markdown-pronunciation" name="pronunciation"></a>
497
505
  ## Pronunciation
498
506
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
+ require_relative "zeitwerk/real_mod_name"
4
5
  require_relative "zeitwerk/loader"
5
6
  require_relative "zeitwerk/registry"
6
7
  require_relative "zeitwerk/explicit_namespace"
@@ -4,4 +4,7 @@ module Zeitwerk
4
4
 
5
5
  class ReloadingDisabledError < Error
6
6
  end
7
+
8
+ class NameError < ::NameError
9
+ end
7
10
  end
@@ -8,6 +8,8 @@ module Zeitwerk
8
8
  # loading their constant before setup. This is documented.
9
9
  module ExplicitNamespace # :nodoc: all
10
10
  class << self
11
+ include RealModName
12
+
11
13
  # Maps constant paths that correspond to explicit namespaces according to
12
14
  # the file system, to the loader responsible for them.
13
15
  #
@@ -52,21 +54,27 @@ module Zeitwerk
52
54
  tracer.disable if cpaths.empty?
53
55
  end
54
56
  end
57
+
58
+ def tracepoint_class_callback(event)
59
+ # If the class is a singleton class, we won't do anything with it so we
60
+ # can bail out immediately. This is several orders of magnitude faster
61
+ # than accessing its name.
62
+ return if event.self.singleton_class?
63
+
64
+ # Note that it makes sense to compute the hash code unconditionally,
65
+ # because the trace point is disabled if cpaths is empty.
66
+ if loader = cpaths.delete(real_mod_name(event.self))
67
+ loader.on_namespace_loaded(event.self)
68
+ disable_tracer_if_unneeded
69
+ end
70
+ end
55
71
  end
56
72
 
57
73
  @cpaths = {}
58
74
  @mutex = Mutex.new
59
- @tracer = TracePoint.new(:class) do |event|
60
- # If the class is a singleton class, we won't do anything with it so we can bail out immediately.
61
- # This is several orders of magnitude faster than accessing `Module#name`.
62
- next if event.self.singleton_class?
63
75
 
64
- # Note that it makes sense to compute the hash code unconditionally,
65
- # because the trace point is disabled if cpaths is empty.
66
- if loader = cpaths.delete(event.self.name)
67
- loader.on_namespace_loaded(event.self)
68
- disable_tracer_if_unneeded
69
- end
70
- end
76
+ # We go through a method instead of defining a block mainly to have a better
77
+ # label when profiling.
78
+ @tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
71
79
  end
72
80
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- class GemInflector < Inflector
4
+ class GemInflector < Inflector # :nodoc:
5
5
  # @param root_file [String]
6
6
  def initialize(root_file)
7
7
  namespace = File.basename(root_file, ".rb")
@@ -13,7 +13,7 @@ module Zeitwerk
13
13
  # @param _abspath [String]
14
14
  # @return [String]
15
15
  def camelize(basename, _abspath)
16
- basename.split('_').map!(&:capitalize!).join
16
+ basename.split('_').map!(&:capitalize).join
17
17
  end
18
18
  end
19
19
  end
@@ -7,6 +7,7 @@ module Zeitwerk
7
7
  class Loader
8
8
  require_relative "loader/callbacks"
9
9
  include Callbacks
10
+ include RealModName
10
11
 
11
12
  # @return [String]
12
13
  attr_reader :tag
@@ -43,7 +44,7 @@ module Zeitwerk
43
44
  #
44
45
  # @private
45
46
  # @return [Set<String>]
46
- attr_reader :ignored
47
+ attr_reader :ignored_glob_patterns
47
48
 
48
49
  # The actual collection of absolute file and directory names at the time the
49
50
  # ignored glob patterns were expanded. Computed on setup, and recomputed on
@@ -132,7 +133,7 @@ module Zeitwerk
132
133
 
133
134
  @root_dirs = {}
134
135
  @preloads = []
135
- @ignored = Set.new
136
+ @ignored_glob_patterns = Set.new
136
137
  @ignored_paths = Set.new
137
138
  @autoloads = {}
138
139
  @autoloaded_dirs = []
@@ -224,16 +225,12 @@ module Zeitwerk
224
225
  #
225
226
  # @param paths [<String, Pathname, <String, Pathname>>]
226
227
  # @return [void]
227
- def ignore(*paths)
228
- mutex.synchronize { ignored.merge(expand_paths(paths)) }
229
- end
230
-
231
- # @private
232
- # @return [void]
233
- def expand_ignored_glob_patterns
234
- # Note that Dir.glob works with regular file names just fine. That is,
235
- # glob patterns technically need no wildcards.
236
- ignored_paths.replace(ignored.flat_map { |path| Dir.glob(path) })
228
+ def ignore(*glob_patterns)
229
+ glob_patterns = expand_paths(glob_patterns)
230
+ mutex.synchronize do
231
+ ignored_glob_patterns.merge(glob_patterns)
232
+ ignored_paths.merge(expand_glob_patterns(glob_patterns))
233
+ end
237
234
  end
238
235
 
239
236
  # Sets autoloads in the root namespace and preloads files, if any.
@@ -243,7 +240,6 @@ module Zeitwerk
243
240
  mutex.synchronize do
244
241
  break if @setup
245
242
 
246
- expand_ignored_glob_patterns
247
243
  actual_root_dirs.each { |root_dir| set_autoloads_in_dir(root_dir, Object) }
248
244
  do_preload
249
245
 
@@ -325,6 +321,7 @@ module Zeitwerk
325
321
  def reload
326
322
  if reloading_enabled?
327
323
  unload
324
+ recompute_ignored_paths
328
325
  setup
329
326
  else
330
327
  raise ReloadingDisabledError, "can't reload, please call loader.enable_reloading before setup"
@@ -342,16 +339,20 @@ module Zeitwerk
342
339
  break if @eager_loaded
343
340
 
344
341
  queue = actual_root_dirs.reject { |dir| eager_load_exclusions.member?(dir) }
345
- while dir = queue.shift
346
- const_get_if_autoload(dir)
342
+ queue.map! { |dir| [Object, dir] }
343
+ while to_eager_load = queue.shift
344
+ namespace, dir = to_eager_load
347
345
 
348
- each_abspath(dir) do |abspath|
346
+ ls(dir) do |basename, abspath|
349
347
  next if eager_load_exclusions.member?(abspath)
350
348
 
351
349
  if ruby?(abspath)
352
- const_get_if_autoload(abspath)
353
- elsif dir?(abspath)
354
- queue << abspath
350
+ if cref = autoloads[File.realpath(abspath)]
351
+ cref[0].const_get(cref[1], false)
352
+ end
353
+ elsif dir?(abspath) && !root_dirs.key?(abspath)
354
+ cname = inflector.camelize(basename, abspath)
355
+ queue << [namespace.const_get(cname, false), abspath]
355
356
  end
356
357
  end
357
358
  end
@@ -391,6 +392,29 @@ module Zeitwerk
391
392
  to_unload.keys.freeze
392
393
  end
393
394
 
395
+ # Logs to `$stdout`, handy shortcut for debugging.
396
+ #
397
+ # @return [void]
398
+ def log!
399
+ @logger = ->(msg) { puts msg }
400
+ end
401
+
402
+ # @private
403
+ # @param dir [String]
404
+ # @return [Boolean]
405
+ def manages?(dir)
406
+ dir = dir + "/"
407
+ ignored_paths.each do |ignored_path|
408
+ return false if dir.start_with?(ignored_path + "/")
409
+ end
410
+
411
+ root_dirs.each_key do |root_dir|
412
+ return true if root_dir.start_with?(dir) || dir.start_with?(root_dir + "/")
413
+ end
414
+
415
+ false
416
+ end
417
+
394
418
  # --- Class methods ---------------------------------------------------------------------------
395
419
 
396
420
  class << self
@@ -414,7 +438,7 @@ module Zeitwerk
414
438
  #
415
439
  # @return [Zeitwerk::Loader]
416
440
  def for_gem
417
- called_from = caller_locations.first.path
441
+ called_from = caller_locations(1, 1).first.path
418
442
  Registry.loader_for_gem(called_from)
419
443
  end
420
444
 
@@ -449,18 +473,39 @@ module Zeitwerk
449
473
  # @param parent [Module]
450
474
  # @return [void]
451
475
  def set_autoloads_in_dir(dir, parent)
452
- each_abspath(dir) do |abspath|
453
- cname = inflector.camelize(File.basename(abspath, ".rb"), abspath).to_sym
454
- if ruby?(abspath)
455
- autoload_file(parent, cname, abspath)
456
- elsif dir?(abspath)
457
- # In a Rails application, `app/models/concerns` is a subdirectory of
458
- # `app/models`, but both of them are root directories.
459
- #
460
- # To resolve the ambiguity file name -> constant path this introduces,
461
- # the `app/models/concerns` directory is totally ignored as a namespace,
462
- # it counts only as root. The guard checks that.
463
- autoload_subdir(parent, cname, abspath) unless root_dirs.key?(abspath)
476
+ ls(dir) do |basename, abspath|
477
+ begin
478
+ if ruby?(basename)
479
+ basename.slice!(-3, 3)
480
+ cname = inflector.camelize(basename, abspath).to_sym
481
+ autoload_file(parent, cname, abspath)
482
+ elsif dir?(abspath)
483
+ # In a Rails application, `app/models/concerns` is a subdirectory of
484
+ # `app/models`, but both of them are root directories.
485
+ #
486
+ # To resolve the ambiguity file name -> constant path this introduces,
487
+ # the `app/models/concerns` directory is totally ignored as a namespace,
488
+ # it counts only as root. The guard checks that.
489
+ unless root_dirs.key?(abspath)
490
+ cname = inflector.camelize(basename, abspath).to_sym
491
+ autoload_subdir(parent, cname, abspath)
492
+ end
493
+ end
494
+ rescue ::NameError => error
495
+ path_type = ruby?(abspath) ? "file" : "directory"
496
+
497
+ raise NameError, <<~MESSAGE
498
+ #{error.message} inferred by #{inflector.class} from #{path_type}
499
+
500
+ #{abspath}
501
+
502
+ Possible ways to address this:
503
+
504
+ * Tell Zeitwerk to ignore this particular #{path_type}.
505
+ * Tell Zeitwerk to ignore one of its parent directories.
506
+ * Rename the #{path_type} to comply with the naming conventions.
507
+ * Modify the inflector to handle this case.
508
+ MESSAGE
464
509
  end
465
510
  end
466
511
  end
@@ -605,7 +650,7 @@ module Zeitwerk
605
650
  # @param dir [String]
606
651
  # @return [void]
607
652
  def do_preload_dir(dir)
608
- each_abspath(dir) do |abspath|
653
+ ls(dir) do |_basename, abspath|
609
654
  do_preload_abspath(abspath)
610
655
  end
611
656
  end
@@ -621,17 +666,17 @@ module Zeitwerk
621
666
  # @param cname [Symbol]
622
667
  # @return [String]
623
668
  def cpath(parent, cname)
624
- parent.equal?(Object) ? cname.to_s : "#{parent.name}::#{cname}"
669
+ parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
625
670
  end
626
671
 
627
672
  # @param dir [String]
628
- # @yieldparam path [String]
673
+ # @yieldparam path [String, String]
629
674
  # @return [void]
630
- def each_abspath(dir)
631
- Dir.foreach(dir) do |entry|
632
- next if entry.start_with?(".")
633
- abspath = File.join(dir, entry)
634
- yield abspath unless ignored_paths.member?(abspath)
675
+ def ls(dir)
676
+ Dir.foreach(dir) do |basename|
677
+ next if basename.start_with?(".")
678
+ abspath = File.join(dir, basename)
679
+ yield basename, abspath unless ignored_paths.member?(abspath)
635
680
  end
636
681
  end
637
682
 
@@ -650,7 +695,20 @@ module Zeitwerk
650
695
  # @param paths [<String, Pathname, <String, Pathname>>]
651
696
  # @return [<String>]
652
697
  def expand_paths(paths)
653
- Array(paths).flatten.map! { |path| File.expand_path(path) }
698
+ paths.flatten.map! { |path| File.expand_path(path) }
699
+ end
700
+
701
+ # @param glob_patterns [<String>]
702
+ # @return [<String>]
703
+ def expand_glob_patterns(glob_patterns)
704
+ # Note that Dir.glob works with regular file names just fine. That is,
705
+ # glob patterns technically need no wildcards.
706
+ glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
707
+ end
708
+
709
+ # @return [void]
710
+ def recompute_ignored_paths
711
+ ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
654
712
  end
655
713
 
656
714
  # @param message [String]
@@ -664,12 +722,6 @@ module Zeitwerk
664
722
  parent.const_defined?(cname, false)
665
723
  end
666
724
 
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
-
673
725
  def register_explicit_namespace(cpath)
674
726
  ExplicitNamespace.register(cpath, self)
675
727
  end
@@ -677,16 +729,12 @@ module Zeitwerk
677
729
  def raise_if_conflicting_directory(dir)
678
730
  self.class.mutex.synchronize do
679
731
  Registry.loaders.each do |loader|
680
- next if loader == self
681
-
682
- loader.dirs.each do |already_managed_dir|
683
- if dir.start_with?(already_managed_dir) || already_managed_dir.start_with?(dir)
684
- require "pp"
685
- raise Error,
686
- "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
687
- " which is already managed by\n\n#{loader.pretty_inspect}\n"
688
- EOS
689
- end
732
+ if loader != self && loader.manages?(dir)
733
+ require "pp"
734
+ raise Error,
735
+ "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
736
+ " which is already managed by\n\n#{loader.pretty_inspect}\n"
737
+ EOS
690
738
  end
691
739
  end
692
740
  end
@@ -1,4 +1,6 @@
1
1
  module Zeitwerk::Loader::Callbacks
2
+ include Zeitwerk::RealModName
3
+
2
4
  # Invoked from our decorated Kernel#require when a managed file is autoloaded.
3
5
  #
4
6
  # @private
@@ -60,7 +62,7 @@ module Zeitwerk::Loader::Callbacks
60
62
  # @param namespace [Module]
61
63
  # @return [void]
62
64
  def on_namespace_loaded(namespace)
63
- if subdirs = lazy_subdirs.delete(namespace.name)
65
+ if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
64
66
  subdirs.each do |subdir|
65
67
  set_autoloads_in_dir(subdir, namespace)
66
68
  end
@@ -0,0 +1,15 @@
1
+ module Zeitwerk::RealModName
2
+ UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
3
+ private_constant :UNBOUND_METHOD_MODULE_NAME
4
+
5
+ # Returns the real name of the class or module, as set after the first
6
+ # constant to which it was assigned (or nil).
7
+ #
8
+ # The name method can be overridden, hence the indirection in this method.
9
+ #
10
+ # @param mod [Class, Module]
11
+ # @return [String, nil]
12
+ def real_mod_name(mod)
13
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.1.5"
4
+ VERSION = "2.1.10"
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.5
4
+ version: 2.1.10
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-24 00:00:00.000000000 Z
11
+ date: 2019-09-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -29,6 +29,7 @@ files:
29
29
  - lib/zeitwerk/kernel.rb
30
30
  - lib/zeitwerk/loader.rb
31
31
  - lib/zeitwerk/loader/callbacks.rb
32
+ - lib/zeitwerk/real_mod_name.rb
32
33
  - lib/zeitwerk/registry.rb
33
34
  - lib/zeitwerk/version.rb
34
35
  homepage: https://github.com/fxn/zeitwerk
@@ -50,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
51
  - !ruby/object:Gem::Version
51
52
  version: '0'
52
53
  requirements: []
53
- rubygems_version: 3.0.1
54
+ rubygems_version: 3.0.3
54
55
  signing_key:
55
56
  specification_version: 4
56
57
  summary: Efficient and thread-safe constant autoloader