infusible 3.4.0 → 3.5.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 +36 -61
- data/infusible.gemspec +1 -1
- data/lib/infusible/constructor.rb +17 -17
- data/lib/infusible/dependency_map.rb +12 -12
- data/lib/infusible/errors/duplicate_dependency.rb +2 -2
- data/lib/infusible/stub.rb +15 -2
- data/lib/infusible.rb +6 -1
- data.tar.gz.sig +0 -0
- metadata +3 -3
- 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: bc25967c984d1fa86abba7a1319a91da40f62e2eec3a904ef93dee7e60a54dfc
|
4
|
+
data.tar.gz: a086e0fd8e9ec8d644e10fbec3f30e6465666358b6f4c190010a8c3ed5105246
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58e0efa11b9c780f8d018c178cbb5e1f0ac1c56cc4b9b2919b3e79f4949fbc0833304c00f4284895b4f9212b62577bbbdc6f029fb5dfb2c503eb0740320c65c5
|
7
|
+
data.tar.gz: f38b08ea1b7a418e1c0c3afc853e1b8fdd184a8880d7699f4320f234bb10c03f0e5e8ed59330f5f4af1ac747d0c3bdbbe01e9282bb3f7ee09aebcd36fd114288
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -3,18 +3,18 @@
|
|
3
3
|
:figure-caption!:
|
4
4
|
|
5
5
|
:dependency_injection_containers_link: link:https://alchemists.io/articles/dependency_injection_containers[Dependency Injection Containers]
|
6
|
-
:
|
6
|
+
:containable_link: link:https://alchemists.io/projects/containable[Containable]
|
7
7
|
:http_link: link:https://github.com/httprb/http[HTTP]
|
8
8
|
|
9
9
|
= Infusible
|
10
10
|
|
11
11
|
Automatically injects dependencies within your object via the _Dependency Inversion Principle_ -- the _D_ in _SOLID_ design -- and is a powerful way to compose complex architectures from small objects which leverage the _Single Responsibility Principle_ -- the _S_ in _SOLID_ design.
|
12
12
|
|
13
|
-
When coupled with {dependency_injection_containers_link}, as provided by the {
|
13
|
+
When coupled with {dependency_injection_containers_link}, as provided by the {containable_link} gem, Infusible completes the second half of the _Dependency Inversion Principle_. Here's a quick example of Infusible in action:
|
14
14
|
|
15
15
|
[source,ruby]
|
16
16
|
----
|
17
|
-
Import = Infusible
|
17
|
+
Import = Infusible[a: 1, b: 2, c: 3]
|
18
18
|
|
19
19
|
class Demo
|
20
20
|
include Import[:a, :b, :c]
|
@@ -87,19 +87,20 @@ Let's walk through each staring by defining a container of dependencies.
|
|
87
87
|
|
88
88
|
==== Containers
|
89
89
|
|
90
|
-
A container provides a common object for which you can group related dependencies for injection and reuse. {
|
90
|
+
A container provides a common object for which you can group related dependencies for injection and reuse. {containable_link} is recommended for defining your dependencies but a primitive `Hash` or any object which responds to the `#[]` message works too.
|
91
91
|
|
92
|
-
For documentation purposes, the {
|
92
|
+
For documentation purposes, the {containable_link} gem will be used. The following creates a simple container where you might want to use the {http_link} gem to make HTTP requests and log information using Ruby's native logger.
|
93
93
|
|
94
94
|
[source,ruby]
|
95
95
|
----
|
96
|
+
require "containable"
|
96
97
|
require "http"
|
97
98
|
require "logger"
|
98
99
|
|
99
100
|
module Container
|
100
|
-
extend
|
101
|
+
extend Containable
|
101
102
|
|
102
|
-
register
|
103
|
+
register :http, HTTP
|
103
104
|
register(:logger) { Logger.new STDOUT }
|
104
105
|
end
|
105
106
|
----
|
@@ -112,7 +113,7 @@ Once your container is defined, you'll want to define the corresponding injector
|
|
112
113
|
----
|
113
114
|
require "infusible"
|
114
115
|
|
115
|
-
Import = Infusible
|
116
|
+
Import = Infusible[Container]
|
116
117
|
----
|
117
118
|
|
118
119
|
==== Dependencies
|
@@ -304,14 +305,14 @@ There is no `+#private+` method since `#[]` does this for you and is _recommende
|
|
304
305
|
[source,ruby]
|
305
306
|
----
|
306
307
|
module Container
|
307
|
-
extend
|
308
|
+
extend Containable
|
308
309
|
|
309
|
-
register
|
310
|
-
register
|
311
|
-
register
|
310
|
+
register :one, "One"
|
311
|
+
register :two, "Two"
|
312
|
+
register :three, "Three"
|
312
313
|
end
|
313
314
|
|
314
|
-
Import = Infusible
|
315
|
+
Import = Infusible[Container]
|
315
316
|
|
316
317
|
class Demo
|
317
318
|
include Import.public(:one)
|
@@ -326,27 +327,27 @@ demo.two # NoMethodError: protected method.
|
|
326
327
|
demo.three # NoMethodError: private method.
|
327
328
|
----
|
328
329
|
|
329
|
-
==== Infused
|
330
|
+
==== Infused Keys
|
330
331
|
|
331
|
-
You have access to the
|
332
|
+
You have access to the keys of all dependencies via the _private_ `#infused_keys` method which is powerful in metaprogramming situations. For example, consider the following which calls all injected dependencies since they have the same Object API (i.e. `#call`):
|
332
333
|
|
333
334
|
Example:
|
334
335
|
|
335
336
|
[source,ruby]
|
336
337
|
----
|
337
338
|
module Container
|
338
|
-
extend
|
339
|
+
extend Containable
|
339
340
|
|
340
|
-
register
|
341
|
-
register
|
341
|
+
register :one, "One"
|
342
|
+
register :two, "Two"
|
342
343
|
end
|
343
344
|
|
344
|
-
Import = Infusible
|
345
|
+
Import = Infusible[Container]
|
345
346
|
|
346
347
|
class Demo
|
347
348
|
include Import[:one, :two]
|
348
349
|
|
349
|
-
def call =
|
350
|
+
def call = infused_keys.each { |key| puts __send__(key) }
|
350
351
|
end
|
351
352
|
|
352
353
|
Demo.new.call
|
@@ -354,29 +355,27 @@ Demo.new.call
|
|
354
355
|
# Two
|
355
356
|
----
|
356
357
|
|
357
|
-
As you can see, with the _private_ `#
|
358
|
+
As you can see, with the _private_ `#infused_keys` attribute reader, we are able to iterate through each infused key and send the `#call` message to each injected dependency.
|
358
359
|
|
359
|
-
Since `#
|
360
|
+
Since `#infused_keys` is a private attribute reader, this means the infused keys are private to each instance. This includes all ancestors when using inheritance as each parent class in the hierarchy will have it's own unique array of infused jk depending on what was injected for that object.
|
360
361
|
|
361
|
-
All infused
|
362
|
+
All infused keys are frozen by default.
|
362
363
|
|
363
364
|
=== Tests
|
364
365
|
|
365
|
-
As you architect your implementation, you'll want to test your injected dependencies. You might want to stub, mock, or spy on them as well. Test support is
|
366
|
-
|
367
|
-
Let's say you have the following implementation that combines both {dry-container_link} (or a primitive `Hash` would work too) and this gem:
|
366
|
+
As you architect your implementation, you'll want to test your injected dependencies. You might want to stub, mock, or spy on them as well. Test support is primarily provided via the {containable_link} gem. Example:
|
368
367
|
|
369
368
|
[source,ruby]
|
370
369
|
----
|
371
370
|
# Our container with a single dependency.
|
372
371
|
module Container
|
373
|
-
extend
|
372
|
+
extend Containable
|
374
373
|
|
375
|
-
register
|
374
|
+
register :kernel, Kernel
|
376
375
|
end
|
377
376
|
|
378
377
|
# Our import which defines our container for potential injection.
|
379
|
-
Import = Infusible
|
378
|
+
Import = Infusible[Container]
|
380
379
|
|
381
380
|
# Our action class which injects our kernel dependency from our container.
|
382
381
|
class Action
|
@@ -390,21 +389,14 @@ With our implementation defined, we can test as follows:
|
|
390
389
|
|
391
390
|
[source,ruby]
|
392
391
|
----
|
393
|
-
# Required: You must require Dry Container and Infusible stubbing for testing purposes.
|
394
|
-
require "dry/container/stub"
|
395
|
-
require "infusible/stub"
|
396
|
-
|
397
392
|
RSpec.describe Action do
|
398
|
-
# Required: You must refine Infusible to leverage stubbing of your dependencies.
|
399
|
-
using Infusible::Stub
|
400
|
-
|
401
393
|
subject(:action) { Action.new }
|
402
394
|
|
403
395
|
let(:kernel) { class_spy Kernel }
|
404
396
|
|
405
|
-
|
406
|
-
|
407
|
-
after {
|
397
|
+
before { Container.stub! kernel: }
|
398
|
+
|
399
|
+
after { Container.restore }
|
408
400
|
|
409
401
|
describe "#call" do
|
410
402
|
it "prints message" do
|
@@ -422,16 +414,12 @@ While the above works great for a single spec, over time you'll want to reduce d
|
|
422
414
|
[source,ruby]
|
423
415
|
----
|
424
416
|
# spec/support/shared_contexts/application_container.rb
|
425
|
-
require "dry/container/stub"
|
426
|
-
require "infusible/stub"
|
427
|
-
|
428
417
|
RSpec.shared_context "with application dependencies" do
|
429
|
-
using Infusible::Stub
|
430
|
-
|
431
418
|
let(:kernel) { class_spy Kernel }
|
432
419
|
|
433
|
-
before {
|
434
|
-
|
420
|
+
before { Container.stub! kernel: }
|
421
|
+
|
422
|
+
after { Container.restore }
|
435
423
|
end
|
436
424
|
----
|
437
425
|
|
@@ -452,20 +440,7 @@ RSpec.describe Action do
|
|
452
440
|
end
|
453
441
|
----
|
454
442
|
|
455
|
-
A shared context allows
|
456
|
-
|
457
|
-
In both spec examples -- so far -- you'll notice only RSpec `before` and `after` blocks are used. You can use an `around` block too. Example:
|
458
|
-
|
459
|
-
[source,ruby]
|
460
|
-
----
|
461
|
-
around do |example|
|
462
|
-
Import.stub_with kernel: FakeKernel do
|
463
|
-
example.run
|
464
|
-
end
|
465
|
-
end
|
466
|
-
----
|
467
|
-
|
468
|
-
⚠️ I mention `around` block support last because the caveat is that you can't use an `around` block with any RSpec test double since link:https://github.com/rspec/rspec-mocks/issues/1283[RSpec can't guarantee proper cleanup]. This is why the RSpec `before` and `after` blocks were used to guarantee proper setup and teardown. That said, you can use _fakes_ or any object you own which _isn't_ a RSpec test double but provides the Object API you need for testing purposes.
|
443
|
+
A shared context allows for reuse across multiple specs by including it as needed.
|
469
444
|
|
470
445
|
== Development
|
471
446
|
|
@@ -524,7 +499,7 @@ Your constructor, initializer, and instance variables are all there. Only you do
|
|
524
499
|
|
525
500
|
=== Style Guide
|
526
501
|
|
527
|
-
When using this gem, along with a container like {
|
502
|
+
When using this gem, along with a container like {containable_link}, make sure to adhere to the following guidelines:
|
528
503
|
|
529
504
|
* Use containers to group related dependencies that make logical sense for the namespace you are working in and avoid using containers as a junk drawer for throwing random objects in.
|
530
505
|
* Use containers that don't have a lot of registered dependencies. If you register too many dependencies, that means your objects are too complex and need to be simplified further.
|
data/infusible.gemspec
CHANGED
@@ -6,16 +6,16 @@ module Infusible
|
|
6
6
|
# Provides the automatic and complete resolution of all injected dependencies.
|
7
7
|
# :reek:TooManyInstanceVariables
|
8
8
|
class Constructor < Module
|
9
|
-
def self.define_instance_variables target,
|
9
|
+
def self.define_instance_variables target, keys, keywords
|
10
10
|
unless target.instance_variable_defined? :@infused_keys
|
11
|
-
target.instance_variable_set :@infused_names,
|
12
|
-
target.instance_variable_set :@infused_keys,
|
11
|
+
target.instance_variable_set :@infused_names, keys
|
12
|
+
target.instance_variable_set :@infused_keys, keys
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
next unless keywords.key?(
|
15
|
+
keys.each do |key|
|
16
|
+
next unless keywords.key?(key) || !target.instance_variable_defined?(:"@#{key}")
|
17
17
|
|
18
|
-
target.instance_variable_set :"@#{
|
18
|
+
target.instance_variable_set :"@#{key}", keywords[key]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -77,39 +77,39 @@ module Infusible
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def define_initialize_with_positionals super_parameters, variablizer
|
80
|
-
instance_module.module_exec dependencies.
|
80
|
+
instance_module.module_exec dependencies.keys, variablizer do |keys, definer|
|
81
81
|
define_method :initialize do |*positionals, **keywords, &block|
|
82
|
-
definer.call self,
|
82
|
+
definer.call self, keys, keywords
|
83
83
|
|
84
84
|
if super_parameters.only_single_splats?
|
85
85
|
super(*positionals, **keywords, &block)
|
86
86
|
else
|
87
|
-
super(*positionals, **super_parameters.keyword_slice(keywords, keys:
|
87
|
+
super(*positionals, **super_parameters.keyword_slice(keywords, keys:), &block)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
93
|
def define_initialize_with_keywords super_parameters, variablizer
|
94
|
-
instance_module.module_exec dependencies.
|
94
|
+
instance_module.module_exec dependencies.keys, variablizer do |keys, definer|
|
95
95
|
define_method :initialize do |**keywords, &block|
|
96
|
-
definer.call self,
|
97
|
-
super(**super_parameters.keyword_slice(keywords, keys:
|
96
|
+
definer.call self, keys, keywords
|
97
|
+
super(**super_parameters.keyword_slice(keywords, keys:), &block)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
102
|
def define_readers
|
103
|
-
methods = dependencies.
|
103
|
+
methods = dependencies.keys.map { |key| ":#{key}" }
|
104
104
|
computed_scope = METHOD_SCOPES.include?(scope) ? scope : :private
|
105
105
|
|
106
106
|
instance_module.module_eval <<-READERS, __FILE__, __LINE__ + 1
|
107
|
-
attr_reader :
|
107
|
+
attr_reader :infused_keys
|
108
108
|
|
109
|
-
def
|
110
|
-
warn "Inusible
|
109
|
+
def infused_names
|
110
|
+
warn "`Inusible#infused_names` is deprecated, use `#infused_keys` instead.",
|
111
111
|
category: :deprecated
|
112
|
-
@
|
112
|
+
@infused_names
|
113
113
|
end
|
114
114
|
|
115
115
|
#{computed_scope} attr_reader #{methods.join ", "}
|
@@ -3,9 +3,9 @@
|
|
3
3
|
module Infusible
|
4
4
|
# Sanitizes and resolves dependencies for use.
|
5
5
|
class DependencyMap
|
6
|
-
PATTERNS = {
|
6
|
+
PATTERNS = {key: /([a-z_][a-zA-Z_0-9]*)$/, valid: /^[\w.]+$/}.freeze
|
7
7
|
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :keys
|
9
9
|
|
10
10
|
def initialize *configuration, patterns: PATTERNS
|
11
11
|
@patterns = patterns
|
@@ -13,10 +13,10 @@ module Infusible
|
|
13
13
|
|
14
14
|
aliases = configuration.last.is_a?(Hash) ? configuration.pop : {}
|
15
15
|
|
16
|
-
configuration.each { |identifier| add
|
17
|
-
aliases.each { |
|
16
|
+
configuration.each { |identifier| add to_key(identifier), identifier }
|
17
|
+
aliases.each { |key, identifier| add key, identifier }
|
18
18
|
|
19
|
-
@
|
19
|
+
@keys = collection.keys.freeze
|
20
20
|
end
|
21
21
|
|
22
22
|
def to_h = collection
|
@@ -25,20 +25,20 @@ module Infusible
|
|
25
25
|
|
26
26
|
attr_reader :patterns, :collection
|
27
27
|
|
28
|
-
def
|
29
|
-
|
28
|
+
def to_key identifier
|
29
|
+
key = identifier[patterns.fetch(:key)]
|
30
30
|
|
31
|
-
return
|
31
|
+
return key if key && key.match?(patterns.fetch(:valid))
|
32
32
|
|
33
33
|
fail(Errors::InvalidDependency.new(identifier:))
|
34
34
|
end
|
35
35
|
|
36
|
-
def add
|
37
|
-
|
36
|
+
def add key, identifier
|
37
|
+
key = key.to_sym
|
38
38
|
|
39
|
-
return collection[
|
39
|
+
return collection[key] = identifier unless collection.key? key
|
40
40
|
|
41
|
-
fail Errors::DuplicateDependency.new
|
41
|
+
fail Errors::DuplicateDependency.new key:, identifier:
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -4,8 +4,8 @@ module Infusible
|
|
4
4
|
module Errors
|
5
5
|
# Prevents duplicate dependencies from being injected.
|
6
6
|
class DuplicateDependency < StandardError
|
7
|
-
def initialize
|
8
|
-
super "Remove #{identifier.inspect} since it's a duplicate of #{
|
7
|
+
def initialize key:, identifier:
|
8
|
+
super "Remove #{identifier.inspect} since it's a duplicate of #{key.inspect}."
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/infusible/stub.rb
CHANGED
@@ -7,14 +7,27 @@ module Infusible
|
|
7
7
|
module Stub
|
8
8
|
refine Actuator do
|
9
9
|
def stub_with(pairs, &)
|
10
|
+
warn "`#{self.class}##{__method__}` is deprecated, use the Containable gem instead.",
|
11
|
+
category: :deprecated
|
12
|
+
|
10
13
|
return unless block_given?
|
11
14
|
|
12
15
|
container.is_a?(Hash) ? stub_hash_with(pairs, &) : stub_container_with(pairs, &)
|
13
16
|
end
|
14
17
|
|
15
|
-
def stub
|
18
|
+
def stub pairs
|
19
|
+
warn "`#{self.class}##{__method__}` is deprecated, use the Containable gem instead",
|
20
|
+
category: :deprecated
|
21
|
+
|
22
|
+
container.is_a?(Hash) ? stub_hash(pairs) : stub_container(pairs)
|
23
|
+
end
|
16
24
|
|
17
|
-
def unstub(*keys)
|
25
|
+
def unstub(*keys)
|
26
|
+
warn "`#{self.class}##{__method__}` is deprecated, use the Containable gem instead",
|
27
|
+
category: :deprecated
|
28
|
+
|
29
|
+
container.is_a?(Hash) ? unstub_hash(*keys) : unstub_container(*keys)
|
30
|
+
end
|
18
31
|
|
19
32
|
private
|
20
33
|
|
data/lib/infusible.rb
CHANGED
@@ -10,5 +10,10 @@ require "infusible/errors/invalid_dependency"
|
|
10
10
|
module Infusible
|
11
11
|
METHOD_SCOPES = %i[public protected private].freeze
|
12
12
|
|
13
|
-
def self.
|
13
|
+
def self.[](container) = Actuator.new container
|
14
|
+
|
15
|
+
def self.with container
|
16
|
+
warn "`Infusible.#{__method__}` is deprecated, use `.[]` instead.", category: :deprecated
|
17
|
+
Actuator.new container
|
18
|
+
end
|
14
19
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: infusible
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brooke Kuhlmann
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
|
36
36
|
gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2024-03
|
38
|
+
date: 2024-04-03 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: marameters
|
@@ -96,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
requirements: []
|
99
|
-
rubygems_version: 3.5.
|
99
|
+
rubygems_version: 3.5.7
|
100
100
|
signing_key:
|
101
101
|
specification_version: 4
|
102
102
|
summary: An automated dependency manager and injector.
|
metadata.gz.sig
CHANGED
Binary file
|