containable 0.6.0 → 1.1.0

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: e00775146489a9efd09bc8bdc42f66e88a68a7c6926a4f607577a7803bd931db
4
- data.tar.gz: 4d4598a1075e508373691657378c447f8f026a7d6175262d350a4c5a13a06a90
3
+ metadata.gz: 14490c83a49633550ef45044720f8f42dee80bbcc8cca3824347974d5583c8c9
4
+ data.tar.gz: dc892f9dfd4257d49401209d0aca17e8cc17b7c6216d024773d9736b66d2d7fb
5
5
  SHA512:
6
- metadata.gz: b3ecc1217f077b5271888013c127509e5d529175888f2c0d68022ef86afb1fed9468166c232668ca25decf5ee81f85bf5cd730b2869c6159466f5fca14ebff68
7
- data.tar.gz: 9c153e67b57f12027c811de578daa9587fd54f84fe4d1b043ed558f1127a2aa57fe520e0416db1d5eecf62e87454387c670061a79538a6536a4ae1af61c080cf
6
+ metadata.gz: 5526cfe5e1816ecc3fc76e7ba0f24d7d88e95a74e2323c8bef12ee0eb609eef75b81a73c263edcd758d725cf4e8e3447dbb747051609d7c8e1a531deb5d6b84b
7
+ data.tar.gz: 5fbc220cb0933840900e8e8094748c9b323ce6fa8a180e398177e29cafad1a166d9406adcd329fac227632f0740aa484002a2a49f254dbd179997c5dc873e40f
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -97,7 +97,7 @@ This is important since containers are only meant to hold your dependencies and
97
97
 
98
98
  === Registration
99
99
 
100
- As shown above, the best way to register dependencies is as you define your container. The most basic is via a key/value pair:
100
+ The best way to register dependencies is when you define your container. The most basic is via a key/value pair:
101
101
 
102
102
  [source,ruby]
103
103
  ----
@@ -123,7 +123,7 @@ module Container
123
123
  end
124
124
  ----
125
125
 
126
- In this case the `:demo` key is associated with an instance of an object but the instance _will only be realized when first resolved_. Until the `:demo` key is resolved, the object is not instantiated and remains a closure (more on resolving dependencies shortly). You can also register procs, lambdas, and functions in the same manner:
126
+ In this case the `:demo` key is associated with an instance of an object but the instance _will only be initialized when resolved_. Until the `:demo` key is resolved, the object is not instantiated and remains a closure (see xref:_resolution[Resolution]). You can also register procs, lambdas, and functions in the same manner:
127
127
 
128
128
  [source,ruby]
129
129
  ----
@@ -140,7 +140,26 @@ module Container
140
140
  end
141
141
  ----
142
142
 
143
- As you can see, registration is quite flexible. That said, you only register either a value or closure but not both. For example, if you register both a value _and_ a closure you'll get a warning (as printed as standard error output):
143
+ If you want closures to be cached or be fresh when resolved, use the `as` keyword. Example:
144
+
145
+ [source,ruby]
146
+ ----
147
+ module Container
148
+ extend Containable
149
+
150
+ register :one, as: :cache, proc { Object.new }
151
+ register :two, as: :fresh, proc { Object.new }
152
+ end
153
+ ----
154
+
155
+ Use `:cache` and `:fresh` to direct how your closures will be resolved. Here's what each does:
156
+
157
+ * `+:cache+`: Ensures the same object is answered each time the key is resolved. In the above example, this means the `one` dependency will always answer the _same_ instance of an `Object` when resolved. This is default behavior so you don't need to define this key and is only shown for explicit illustration purposes.
158
+ * `+:fresh+`: Ensures a new object is answered each time the key is resolved. In the above example, this means that the `two` dependency will always answer a _different_ instance of an `Object`. You want to use this when you want to lazily resolve a dependency while still wanting a new instance each time.
159
+
160
+ 💡 The `as` key is only applied when using a closure with no parameters and is ignored otherwise. This means you don't need to supply this key when using literals.
161
+
162
+ As you can see, registration is quite flexible. That said, you only need to register a value or closure but not both. For example, if you register both a value _and_ a closure you'll get a warning (as printed as standard error output):
144
163
 
145
164
  [source,ruby]
146
165
  ----
@@ -173,9 +192,9 @@ Container.register :two, 2
173
192
  Container[:three] = 3
174
193
  ----
175
194
 
176
- With the above, a combination of `.register` and `.[]=` (setter) messages are used. While the latter is handy the former should be preferred for improved readability.
195
+ With the above, a combination of `.register` and `.[]=` (setter) messages are used. While the latter is handy, the former should be preferred for improved readability.
177
196
 
178
- ⚠️ Due to registration being flexible to begin with, avoid nesting closures. Example:
197
+ ⚠️ Due to registration being so flexible, avoid nesting closures. Example:
179
198
 
180
199
  [source,ruby]
181
200
  ----
@@ -190,7 +209,7 @@ While the former will work, there is no benefit to nesting like this. The latter
190
209
 
191
210
  === Resolution
192
211
 
193
- Now that you understand how to register dependencies, we can talk about resolving them. There are two ways to resolve a dependency. Example:
212
+ There are two ways to resolve a dependency. Example:
194
213
 
195
214
  [source,ruby]
196
215
  ----
@@ -198,7 +217,7 @@ Container[:demo]
198
217
  Container.resolve(:demo)
199
218
  ----
200
219
 
201
- Both messages are acceptable but using `.[]` (getter) is recommended due to being succinct, requires less typing, and allows the container to feel more like a `Hash`. Internally, when resolving a dependency, all keys are stored as strings which means you can use symbols or strings interchangeably except when using namespaces (more on this shortly). Example:
220
+ Both messages are acceptable but using `.[]` (getter) is recommended due to being succinct, requires less typing, and allows the container to feel like a `Hash`. Internally, when resolving a dependency, all keys are stored as strings which means you can use symbols or strings interchangeably except when using namespaces (more on this shortly). Example:
202
221
 
203
222
  [source,ruby]
204
223
  ----
@@ -206,9 +225,9 @@ Container[:demo] # "example"
206
225
  Container["demo"] # "example"
207
226
  ----
208
227
 
209
- When discussing registration earlier, we saw you can register values and closures. A value can also be a closure but if a block is registered -- in addition to the value -- the block takes precedence over the value.
228
+ When discussing registration earlier, we saw you can register values and closures. A value can also be a closure but if a block is registered -- in addition to the value -- the block takes precedence.
210
229
 
211
- What hasn't been discussed is the _kind_ of closure used when registering a value or block. If a closure takes _no parameters_, then the closure will be resolved immediately when resolving the key for the first time. Any closure that takes one more more parameters will never be resolved which means you can call the closure directly when needed. To illustrate, consider the following:
230
+ What hasn't been discussed is the _kind_ of closure used when registering a value or block. If a closure has _no parameters_, then the closure will be resolved immediately when resolving the key for the first time. Any closure that takes one more more parameters will never be resolved which means you can call the closure directly when needed. To illustrate, consider the following:
212
231
 
213
232
  [source,ruby]
214
233
  ----
@@ -222,14 +241,14 @@ module Container
222
241
  register :three, -> text { text.reverse }
223
242
  end
224
243
 
225
- Container[:one] # 1
226
- Container[:two] # #<Proc:0x000000012e9f8718 /demo:23>
227
- Container[:three] # #<Proc:0x000000012e9f8628 /demo:24 (lambda)>
244
+ Container[:one] # 1
245
+ Container[:two] # #<Proc:0x000000012e9f8718 /demo:23>
246
+ Container[:three] # #<Proc:0x000000012e9f8628 /demo:24 (lambda)>
228
247
  ----
229
248
 
230
249
  With the above, you can see `:one` was immediately resolved to the value of `1` even though it was wrapped in a closure to begin with. This happened because the closure had no parameters so was safe to resolve. Again, this allows you to lazily resolve a dependency until you need it.
231
250
 
232
- For keys `:two` and `:three`, we have a closure that has at least one parameter so remains a closure so you can supply the arguments you need later. Here's a closer look of using the `:two` and `:three` dependencies:
251
+ For keys `:two` and `:three`, we have a closure that has at least one parameter so remains a closure. This allows you to supply required arguments later. Here's a closer look of using the `:two` and `:three` dependencies:
233
252
 
234
253
  [source,ruby]
235
254
  ----
@@ -239,6 +258,27 @@ Container[:three].call "demo" # "omed"
239
258
 
240
259
  In all of these situations, we have closures supplied as values or blocks but only closures with out parameters are resolved (i.e. unwrapped).
241
260
 
261
+ When using the `as` key, you can control if you get a cached or fresh instance. Example:
262
+
263
+ [source,ruby]
264
+ ----
265
+ require "containable"
266
+
267
+ module Container
268
+ extend Containable
269
+
270
+ register(:one) { Object.new }
271
+ register(:two, as: :fresh) { Object.new }
272
+ end
273
+
274
+ Container[:one] # #<Object:0x000000012d135b90>
275
+ Container[:one] # #<Object:0x000000012d135b90>
276
+ Container[:two] # #<Object:0x000000012d237728>
277
+ Container[:two] # #<Object:0x000000012d2de550>
278
+ ----
279
+
280
+ Notice `one` always answers the same instance of an `Object` while `two` always answers a new instance of `Object`. By using `:fresh`, this allows you to lazily evaluate your closure while disabling default caching support.
281
+
242
282
  === Namespaces
243
283
 
244
284
  As hinted at earlier, you can namespace your dependencies for improved organization. Example:
@@ -278,7 +318,7 @@ Container["three.silver"] # "silver"
278
318
 
279
319
  === Enumeration
280
320
 
281
- Limited enumeration of your container is possible. Given the following:
321
+ Enumeration is possible but limited. Given the following:
282
322
 
283
323
  [source,ruby]
284
324
  ----
@@ -353,7 +393,7 @@ Other.name
353
393
  # "Other"
354
394
  ----
355
395
 
356
- As you can see a container, once duplicated, can be assigned to a local variable or a new constant. When assigning to a variable, the container will use a temporary name of `containable` for identification.
396
+ A container, once duplicated, can be assigned to a local variable or a new constant. When assigning to a variable, the container will default to a temporary name of `containable` for identification.
357
397
 
358
398
  === Clones
359
399
 
@@ -367,14 +407,28 @@ Container.clone.frozen? # true
367
407
 
368
408
  === Customization
369
409
 
370
- You can customize how the container registers and resolves dependencies by creating your own register and resolver objects. For example, here's how to use a custom register that doesn't care if you override an existing key.
410
+ You can customize how the container registers and resolves dependencies by creating your own register and resolver. Internally, both of these objects have access to and use `dependencies` (i.e. `Concurrent::Hash`) which stores the registered key and tuple. Example:
411
+
412
+ [source,ruby]
413
+ ----
414
+ {
415
+ "one" => [1, :cache],
416
+ "two" => [<Proc:0x000000013f613a10>, :fresh]
417
+ }
418
+ ----
419
+
420
+ Each tuple captures the dependency (value) and directive (i.e. `:cache` or `:fresh`). This allows you to have access to all information captured at registration. Below are a few examples on how to use and customize this information for your own purposes.
421
+
422
+ Here's how to use a custom register that doesn't care if you override an existing key.
371
423
 
372
424
  [source,ruby]
373
425
  ----
374
426
  require "containable"
375
427
 
376
428
  class CustomRegister < Containable::Register
377
- def call(key, value = nil, &block) = dependencies[namespacify(key)] = block || value
429
+ def call(key, value = nil, as: :cache, &block)
430
+ dependencies[namespacify(key)] = [block || value, as]
431
+ end
378
432
  end
379
433
 
380
434
  module Container
@@ -387,7 +441,7 @@ end
387
441
  Container[:one] # "override"
388
442
  ----
389
443
 
390
- ...and here's an example with a custom resolver that only allows specific keys to be resolved:
444
+ Here's an example with a custom resolver that only allows specific keys to be resolved:
391
445
 
392
446
  [source,ruby]
393
447
  ----
@@ -423,7 +477,7 @@ Container[:two] # Only use these keys: [:one, :three] (KeyError)
423
477
  Container[:three] # 3
424
478
  ----
425
479
 
426
- In both cases, you only need to inject your custom register or resolver when extending your container with `Containable`. Both of these classes should inherit from either `Containable::Register` or `Containable::Resolver` to customize behavior as you like. Definitely check out the source code of both these classes to learn more and customize as desired.
480
+ In both cases, you only need to inject your custom register or resolver when extending your container with `Containable`. Both of these classes should inherit from either `Containable::Register` or `Containable::Resolver` to customize behavior as you like. Definitely read the source code of both these classes to learn more.
427
481
 
428
482
  === Infusible
429
483
 
@@ -515,7 +569,7 @@ end
515
569
 
516
570
  You'll notice, in all of the examples, only two methods are used: `.stub!` and `.restore`. The first allows you supply keyword arguments of all dependencies you want stubbed. The last ensures your test suite is properly cleaned up so all stubs are removed and the container is restored to it's original state. If you don't restore your container after each spec, you'll end up with stubs leaking across your specs and {rspec_link} will error to the same effect as well.
517
571
 
518
- _Always_ use `.stub!` to set your container up for testing. Once setup, you can add more stubs by using the `.stub` method (without the bang). So, to recap, use `.stub!` as a one-liner for setup and initial stubs then use `.stub` to add more stubs after the fact. Finally, ensure you restore (i.e. `.restore`) your container for proper cleanup after each test.
572
+ _Always_ use `.stub!` to set your container up for testing. Once set up, you can add more stubs by using the `.stub` method (without the bang). So, to recap, use `.stub!` as a one-liner for setup and initial stubs then use `.stub` to add more stubs after the fact. Finally, ensure you restore (i.e. `.restore`) your container for proper cleanup after each test.
519
573
 
520
574
  ‼️ Use of `.stub!`, while convenient for testing, should -- under no circumstances -- be used in production code because it is meant for testing purposes only.
521
575
 
data/containable.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "containable"
5
- spec.version = "0.6.0"
5
+ spec.version = "1.1.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/containable"
@@ -22,8 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.signing_key = Gem.default_key_path
23
23
  spec.cert_chain = [Gem.default_cert_path]
24
24
 
25
- spec.required_ruby_version = ">= 3.3", "<= 3.4"
26
- spec.add_dependency "concurrent-ruby", "~> 1.2"
25
+ spec.required_ruby_version = "~> 3.4"
26
+ spec.add_dependency "concurrent-ruby", "~> 1.3"
27
27
 
28
28
  spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
29
29
  spec.files = Dir["*.gemspec", "lib/**/*"]
@@ -36,10 +36,10 @@ module Containable
36
36
  end
37
37
 
38
38
  def define_register target = register
39
- define_method :register do |key, value = nil, &block|
39
+ define_method :register do |key, value = nil, as: :cache, &block|
40
40
  fail FrozenError, "Can't modify frozen container." if dependencies.frozen?
41
41
 
42
- target.call key, value, &block
42
+ target.call key, value, as:, &block
43
43
  end
44
44
  end
45
45
 
@@ -52,7 +52,7 @@ module Containable
52
52
  end
53
53
 
54
54
  def define_each target = dependencies
55
- define_method(:each) { |&block| target.each(&block) }
55
+ define_method(:each) { |&block| target.transform_values(&:first).each(&block) }
56
56
  end
57
57
 
58
58
  def define_each_key target = dependencies
@@ -76,7 +76,6 @@ module Containable
76
76
  def define_dup target = self.class,
77
77
  local_register: register.class,
78
78
  local_resolver: resolver.class
79
-
80
79
  define_method :dup do
81
80
  instance = target.new dependencies.dup, register: local_register, resolver: local_resolver
82
81
  Module.new.set_temporary_name("containable").extend instance
@@ -3,25 +3,28 @@
3
3
  require "concurrent/hash"
4
4
 
5
5
  module Containable
6
+ # :reek:TooManyInstanceVariables
6
7
  # Registers dependencies for future evaluation.
7
8
  class Register
8
9
  SEPARATOR = "."
10
+ DIRECTIVES = %i[cache fresh].freeze
9
11
 
10
- def initialize dependencies = Concurrent::Hash.new, separator: SEPARATOR
12
+ def initialize dependencies = Concurrent::Hash.new, separator: SEPARATOR, directives: DIRECTIVES
11
13
  @dependencies = dependencies
12
14
  @separator = separator
15
+ @directives = directives
13
16
  @keys = []
14
17
  @depth = 0
15
18
  end
16
19
 
17
- def call key, value = nil, &block
18
- namespaced_key = namespacify key
19
- message = "Dependency is already registered: #{key.inspect}."
20
-
20
+ def call key, value = nil, as: :cache, &block
21
21
  warn "Registration of value is ignored since block takes precedence." if value && block
22
- fail KeyError, message if dependencies.key? namespaced_key
23
22
 
24
- dependencies[namespaced_key] = block || value
23
+ namespaced_key = namespacify key
24
+
25
+ check_duplicate key, namespaced_key
26
+ check_directive as
27
+ dependencies[namespaced_key] = [block || value, as]
25
28
  end
26
29
 
27
30
  alias register call
@@ -34,10 +37,23 @@ module Containable
34
37
 
35
38
  private
36
39
 
37
- attr_reader :dependencies, :separator
40
+ attr_reader :dependencies, :separator, :directives
38
41
 
39
42
  attr_accessor :keys, :depth
40
43
 
44
+ def check_duplicate key, namespaced_key
45
+ message = "Dependency is already registered: #{key.inspect}."
46
+
47
+ fail KeyError, message if dependencies.key? namespaced_key
48
+ end
49
+
50
+ def check_directive value
51
+ return if directives.include? value
52
+
53
+ fail ArgumentError,
54
+ %(Invalid directive: #{value.inspect}. Use #{directives.map(&:inspect).join " or "}.)
55
+ end
56
+
41
57
  def visit &block
42
58
  increment
43
59
  instance_eval(&block) if block
@@ -10,17 +10,29 @@ module Containable
10
10
  end
11
11
 
12
12
  def call key
13
- normalized_key = key.to_s
13
+ tuple = fetch key
14
+ value, as = tuple
14
15
 
15
- value = dependencies.fetch normalized_key do
16
- fail KeyError, "Unable to resolve dependency: #{key.inspect}."
17
- end
16
+ return value unless value.is_a?(Proc) && value.arity.zero?
18
17
 
19
- value.is_a?(Proc) && value.arity.zero? ? dependencies[normalized_key] = value.call : value
18
+ process key, value, as
20
19
  end
21
20
 
22
21
  private
23
22
 
24
23
  attr_reader :dependencies
24
+
25
+ def fetch key
26
+ dependencies.fetch key.to_s do
27
+ fail KeyError, "Unable to resolve dependency: #{key.inspect}."
28
+ end
29
+ end
30
+
31
+ def process key, closure, directive
32
+ value = closure.call
33
+ dependencies[key.to_s] = [value, directive] if directive == :cache
34
+
35
+ value
36
+ end
25
37
  end
26
38
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: containable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain:
11
10
  - |
@@ -35,7 +34,7 @@ cert_chain:
35
34
  3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
36
35
  gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
37
36
  -----END CERTIFICATE-----
38
- date: 2024-11-09 00:00:00.000000000 Z
37
+ date: 2025-01-25 00:00:00.000000000 Z
39
38
  dependencies:
40
39
  - !ruby/object:Gem::Dependency
41
40
  name: concurrent-ruby
@@ -43,15 +42,14 @@ dependencies:
43
42
  requirements:
44
43
  - - "~>"
45
44
  - !ruby/object:Gem::Version
46
- version: '1.2'
45
+ version: '1.3'
47
46
  type: :runtime
48
47
  prerelease: false
49
48
  version_requirements: !ruby/object:Gem::Requirement
50
49
  requirements:
51
50
  - - "~>"
52
51
  - !ruby/object:Gem::Version
53
- version: '1.2'
54
- description:
52
+ version: '1.3'
55
53
  email:
56
54
  - brooke@alchemists.io
57
55
  executables: []
@@ -79,16 +77,12 @@ metadata:
79
77
  label: Containable
80
78
  rubygems_mfa_required: 'true'
81
79
  source_code_uri: https://github.com/bkuhlmann/containable
82
- post_install_message:
83
80
  rdoc_options: []
84
81
  require_paths:
85
82
  - lib
86
83
  required_ruby_version: !ruby/object:Gem::Requirement
87
84
  requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: '3.3'
91
- - - "<="
85
+ - - "~>"
92
86
  - !ruby/object:Gem::Version
93
87
  version: '3.4'
94
88
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -97,8 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
91
  - !ruby/object:Gem::Version
98
92
  version: '0'
99
93
  requirements: []
100
- rubygems_version: 3.5.23
101
- signing_key:
94
+ rubygems_version: 3.6.3
102
95
  specification_version: 4
103
96
  summary: A thread-safe dependency injection container.
104
97
  test_files: []
metadata.gz.sig CHANGED
Binary file