zeitwerk 2.4.1 → 2.4.2

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: b0c408b8f6001e150b16b1e06b1b05d31bcbf4f0144535c995093c822065be3d
4
- data.tar.gz: af26de0ecd68b8ebc1836ea431d59c6d7e10d79937c402e3529fc4275631c950
3
+ metadata.gz: f45a7c3caf48f06e10dd513a7b074da7ec059fa3299751d361514aadc83d995e
4
+ data.tar.gz: c8c29793e95245b47b1283891791d2c20365b8c694b3dcdfb7daab0b74c16bdb
5
5
  SHA512:
6
- metadata.gz: 012c9a70cd31973a7251f46f62c102bf11155a6ea90dcdfa62bb2cd1859fca07cfed268dd130528fc55eab4ea0d440347e7ed0cb78f54dca861f96f56014541d
7
- data.tar.gz: 7243c0f9b6c39dd389f0570fe03f11a8cadb01b74ae5d5efaa9b895ecf3b2717b5eb71e027c555f1cfeb95457dd5f5d8ace4516eb3a77500b05b1c8f973c1a94
6
+ metadata.gz: 6194d326b268c9333ed9d2ac1c62b65756fa9af844c5fb7ad8272ecec33aa439015506758bcd329e421508facb4153e04e45da0efb0a2a42001a6f2e0a0d3b6a
7
+ data.tar.gz: 656009f40e777f641ff1dc80f54b63559514439538c73965aa9226b6c3e33c6be71c07eb30adfe7f4cd4b3be72aa6e42918392ba27167fb9502b9aed037f36d3
data/README.md CHANGED
@@ -25,6 +25,7 @@
25
25
  - [Zeitwerk::Inflector](#zeitwerkinflector)
26
26
  - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
27
27
  - [Custom inflector](#custom-inflector)
28
+ - [The on_load callback](#the-on_load-callback)
28
29
  - [Logging](#logging)
29
30
  - [Loader tag](#loader-tag)
30
31
  - [Ignoring parts of the project](#ignoring-parts-of-the-project)
@@ -502,6 +503,52 @@ class MyGem::Inflector < Zeitwerk::GemInflector
502
503
  end
503
504
  ```
504
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
+
505
552
  <a id="markdown-logging" name="logging"></a>
506
553
  ### Logging
507
554
 
@@ -28,6 +28,7 @@ module Kernel
28
28
  end
29
29
  else
30
30
  loader.on_dir_autoloaded(path)
31
+ true
31
32
  end
32
33
  else
33
34
  zeitwerk_original_require(path).tap do |required|
@@ -129,6 +129,9 @@ module Zeitwerk
129
129
  # @sig Set[String]
130
130
  attr_reader :eager_load_exclusions
131
131
 
132
+ # User-oriented callbacks to be fired when a constant is loaded.
133
+ attr_reader :on_load_callbacks
134
+
132
135
  # @private
133
136
  # @sig Mutex
134
137
  attr_reader :mutex
@@ -155,6 +158,7 @@ module Zeitwerk
155
158
  @to_unload = {}
156
159
  @lazy_subdirs = {}
157
160
  @eager_load_exclusions = Set.new
161
+ @on_load_callbacks = {}
158
162
 
159
163
  # TODO: find a better name for these mutexes.
160
164
  @mutex = Mutex.new
@@ -262,6 +266,24 @@ module Zeitwerk
262
266
  end
263
267
  end
264
268
 
269
+ # Configure a block to be invoked once a certain constant path is loaded.
270
+ # Supports multiple callbacks, and if there are many, they are executed in
271
+ # the order in which they were defined.
272
+ #
273
+ # loader.on_load("SomeApiClient") do
274
+ # SomeApiClient.endpoint = "https://api.dev"
275
+ # end
276
+ #
277
+ # @raise [TypeError]
278
+ # @sig (String) { () -> void } -> void
279
+ def on_load(cpath, &block)
280
+ raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String)
281
+
282
+ mutex.synchronize do
283
+ (on_load_callbacks[cpath] ||= []) << block
284
+ end
285
+ end
286
+
265
287
  # Sets autoloads in the root namespace and preloads files, if any.
266
288
  #
267
289
  # @sig () -> void
@@ -6,15 +6,19 @@ module Zeitwerk::Loader::Callbacks
6
6
  # @private
7
7
  # @sig (String) -> void
8
8
  def on_file_autoloaded(file)
9
- cref = autoloads.delete(file)
10
- to_unload[cpath(*cref)] = [file, cref] if reloading_enabled?
9
+ cref = autoloads.delete(file)
10
+ cpath = cpath(*cref)
11
+
12
+ to_unload[cpath] = [file, cref] if reloading_enabled?
11
13
  Zeitwerk::Registry.unregister_autoload(file)
12
14
 
13
15
  if logger && cdef?(*cref)
14
- log("constant #{cpath(*cref)} loaded from file #{file}")
16
+ log("constant #{cpath} loaded from file #{file}")
15
17
  elsif !cdef?(*cref)
16
- raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath(*cref)}, but didn't", cref.last)
18
+ raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath}, but didn't", cref.last)
17
19
  end
20
+
21
+ run_on_load_callbacks(cpath)
18
22
  end
19
23
 
20
24
  # Invoked from our decorated Kernel#require when a managed directory is
@@ -37,9 +41,10 @@ module Zeitwerk::Loader::Callbacks
37
41
  mutex2.synchronize do
38
42
  if cref = autoloads.delete(dir)
39
43
  autovivified_module = cref[0].const_set(cref[1], Module.new)
40
- log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
44
+ cpath = autovivified_module.name
45
+ log("module #{cpath} autovivified from directory #{dir}") if logger
41
46
 
42
- to_unload[autovivified_module.name] = [dir, cref] if reloading_enabled?
47
+ to_unload[cpath] = [dir, cref] if reloading_enabled?
43
48
 
44
49
  # We don't unregister `dir` in the registry because concurrent threads
45
50
  # wouldn't find a loader associated to it in Kernel#require and would
@@ -48,6 +53,8 @@ module Zeitwerk::Loader::Callbacks
48
53
  autoloaded_dirs << dir
49
54
 
50
55
  on_namespace_loaded(autovivified_module)
56
+
57
+ run_on_load_callbacks(cpath)
51
58
  end
52
59
  end
53
60
  end
@@ -65,4 +72,15 @@ module Zeitwerk::Loader::Callbacks
65
72
  end
66
73
  end
67
74
  end
75
+
76
+ private
77
+
78
+ # @sig (String) -> void
79
+ def run_on_load_callbacks(cpath)
80
+ # Very common, do not even compute a hash code.
81
+ return if on_load_callbacks.empty?
82
+
83
+ callbacks = reloading_enabled? ? on_load_callbacks[cpath] : on_load_callbacks.delete(cpath)
84
+ callbacks.each(&:call) if callbacks
85
+ end
68
86
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.4.1"
4
+ VERSION = "2.4.2"
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.4.1
4
+ version: 2.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-29 00:00:00.000000000 Z
11
+ date: 2020-11-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem