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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +74 -20
- data/containable.gemspec +3 -3
- data/lib/containable/builder.rb +3 -4
- data/lib/containable/register.rb +24 -8
- data/lib/containable/resolver.rb +17 -5
- data.tar.gz.sig +0 -0
- metadata +6 -13
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14490c83a49633550ef45044720f8f42dee80bbcc8cca3824347974d5583c8c9
|
4
|
+
data.tar.gz: dc892f9dfd4257d49401209d0aca17e8cc17b7c6216d024773d9736b66d2d7fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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]
|
226
|
-
Container[:two]
|
227
|
-
Container[:three]
|
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
|
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
|
-
|
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
|
-
|
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
|
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)
|
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
|
-
|
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
|
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
|
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 = "
|
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 = "
|
26
|
-
spec.add_dependency "concurrent-ruby", "~> 1.
|
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/**/*"]
|
data/lib/containable/builder.rb
CHANGED
@@ -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
|
data/lib/containable/register.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/containable/resolver.rb
CHANGED
@@ -10,17 +10,29 @@ module Containable
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def call key
|
13
|
-
|
13
|
+
tuple = fetch key
|
14
|
+
value, as = tuple
|
14
15
|
|
15
|
-
value
|
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
|
-
|
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:
|
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:
|
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.
|
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.
|
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.
|
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
|