infusible 2.1.0 → 2.2.1
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 +47 -17
- data/infusible.gemspec +2 -2
- data/lib/infusible/actuator.rb +4 -0
- data/lib/infusible/constructor.rb +6 -3
- data/lib/infusible.rb +5 -1
- data.tar.gz.sig +0 -0
- metadata +7 -4
- 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: 870b107724590e3bc2299263db39c125af05713798884f8134d535e5b33da4c0
|
4
|
+
data.tar.gz: 80f0b9ece613b463da8fad3d6be1a0f9433e0af99dba4fe899a37fafffbce62a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cef17b734b179f78de093ce80ba28911ab039f33d2c086c70c5a99d063e07a91e860a575989874f77dcd98add7fed5829c36d59b0f35787ca0dd69de8815dc4a
|
7
|
+
data.tar.gz: 8ac52b78bc174842c896d09cd8dbffe070dcec75ac219874a7b52cd37cbca97c2c91297602b229916f102eb94d45110e6053999b5b011f3f5a0cd30ebe570a1b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -2,20 +2,15 @@
|
|
2
2
|
:toclevels: 5
|
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
|
:dry-container_link: link:https://dry-rb.org/gems/dry-container[Dry Container]
|
7
7
|
:http_link: link:https://github.com/httprb/http[HTTP]
|
8
8
|
|
9
9
|
= Infusible
|
10
10
|
|
11
|
-
Automatically
|
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
|
-
This
|
14
|
-
|
15
|
-
* All injected dependencies are _private by default_ in order to not break encapsulation and a _major_ reason why this gem was created.
|
16
|
-
* Uses keyword arguments only while the original Dry AutoInject gem supports positionals or hash arguments in addition to keyword arguments.
|
17
|
-
|
18
|
-
The entire architecture centers on the injection of a _container_ of dependencies. A container can be any object that responds to the `#[]` message and pairs well with the {dry-container_link} gem but a primitive `Hash` works too. Here's a quick example of Infusible in action:
|
13
|
+
This means -- when coupled with {dependency_injection_containers_link} as provided by the {dry-container_link} gem -- Infusible completes the second half of the _Dependency Inversion Principle_. Here's a quick example of Infusible in action:
|
19
14
|
|
20
15
|
[source,ruby]
|
21
16
|
----
|
@@ -30,15 +25,14 @@ end
|
|
30
25
|
puts Demo.new # My injected dependencies are: 1, 2, and 3.
|
31
26
|
----
|
32
27
|
|
33
|
-
By
|
28
|
+
By _infusing_ dependencies into your object, you have the ability to define common dependencies that can be injected without the manual setup normally required to define a constructor, set private instance variables, and set private attribute readers.
|
34
29
|
|
35
30
|
toc::[]
|
36
31
|
|
37
32
|
== Features
|
38
33
|
|
39
|
-
* Ensures injected dependencies are _private by default_.
|
40
|
-
*
|
41
|
-
* Built on top of the link:https://alchemists.io/projects/marameters[Marameters] gem.
|
34
|
+
* Ensures injected dependencies are _private by default_ but has support for public and protected injection.
|
35
|
+
* Built atop the link:https://alchemists.io/projects/marameters[Marameters] gem.
|
42
36
|
|
43
37
|
== Requirements
|
44
38
|
|
@@ -146,7 +140,7 @@ Pinger.new.call "https://duckduckgo.com"
|
|
146
140
|
|
147
141
|
=== Advanced
|
148
142
|
|
149
|
-
When injecting your dependencies you _must_ always define what dependencies you want to require. By default, none will be injected. The following
|
143
|
+
When injecting your dependencies you _must_ always define what dependencies you want to require. By default, none will be injected. The following demonstrates multiple ways to manage the injection of your dependencies.
|
150
144
|
|
151
145
|
==== Keys
|
152
146
|
|
@@ -244,11 +238,11 @@ class Pinger
|
|
244
238
|
end
|
245
239
|
----
|
246
240
|
|
247
|
-
The above will ensure the logger gets passed upwards
|
241
|
+
The above will ensure the logger gets passed upwards to the superclass while remaining accessible by subclass.
|
248
242
|
|
249
243
|
==== Inheritance
|
250
244
|
|
251
|
-
When using inheritance or multiple inheritance, the child class' dependencies will take precedence over the parent's dependencies as long as the keys are the same. Consider the following:
|
245
|
+
When using inheritance (or multiple inheritance), the child class' dependencies will take precedence over the parent's dependencies as long as the keys are the same. Consider the following:
|
252
246
|
|
253
247
|
[source,ruby]
|
254
248
|
----
|
@@ -280,7 +274,7 @@ class Child < Parent
|
|
280
274
|
end
|
281
275
|
----
|
282
276
|
|
283
|
-
Once again, the child's logger will take precedence over the what is provided by default by the parent. This also applies to multiple levels of inheritance or multiple inherited modules. Whichever is last, wins. Lastly, you can mix and match dependencies too:
|
277
|
+
Once again, the child's logger will take precedence over the what is provided by default by the parent. This also applies to multiple levels of inheritance or multiple inherited modules. Whichever is last to be injected, wins. Lastly, you can mix and match dependencies too:
|
284
278
|
|
285
279
|
[source,ruby]
|
286
280
|
----
|
@@ -297,11 +291,46 @@ With the above, the child class will have access to both the `logger` and `http`
|
|
297
291
|
|
298
292
|
⚠️ Be careful when using parent dependencies within your child classes since they are _private by default_. Even though you can reach them, they might change, which can break your downstream dependencies and probably should be avoided or at least defined as `protected` by your parent objects in order to avoid breaking your parent/child relationship.
|
299
293
|
|
294
|
+
==== Scopes
|
295
|
+
|
296
|
+
By default -- and in all of the examples shown so far -- your dependencies are private by default when injected but you can make them public or protected. Here's a quick guide:
|
297
|
+
|
298
|
+
* `include Import[:logger]`: Injects a _private_ logger dependency.
|
299
|
+
* `include Import.protected(logger)`: Injects a _protected_ logger dependency. Useful with inheritance and a subclass needs access to the dependency.
|
300
|
+
* `include Import.public(:logger)`: Injects a _public_ logger dependency.
|
301
|
+
|
302
|
+
There is no `+#private+` method since `#[]` does this for you and is the recommended practice. Use of `#public` and `+#protected+` should be uses sparingly or not at all if you can avoid it. Here's an example where public, protected, and private dependencies are injected:
|
303
|
+
|
304
|
+
[source,ruby]
|
305
|
+
----
|
306
|
+
module Container
|
307
|
+
extend Dry::Container::Mixin
|
308
|
+
|
309
|
+
register(:one) { "One" }
|
310
|
+
register(:two) { "Two" }
|
311
|
+
register(:three) { "Three" }
|
312
|
+
end
|
313
|
+
|
314
|
+
Import = Infusible.with Container
|
315
|
+
|
316
|
+
class Demo
|
317
|
+
include Import.public(:one)
|
318
|
+
include Import.protected(:two)
|
319
|
+
include Import[:three]
|
320
|
+
end
|
321
|
+
|
322
|
+
demo = Demo.new
|
323
|
+
|
324
|
+
demo.one # "One"
|
325
|
+
demo.two # NoMethodError: protected method.
|
326
|
+
demo.three # NoMethodError: private method.
|
327
|
+
----
|
328
|
+
|
300
329
|
=== Tests
|
301
330
|
|
302
331
|
As you architect your implementation, you'll want to test your injected dependencies. You'll also want to stub, mock, or spy on them as well. Testing support is built-in for you by only needing to require the stub refinement as provided by this gem. For demonstration purposes, I'm going to assume you are using RSpec but you can adapt for whatever testing framework you are using.
|
303
332
|
|
304
|
-
Let's say you have the following implementation that combines both {dry-container_link} (or a
|
333
|
+
Let's say you have the following implementation that combines both {dry-container_link} (or a primitive `Hash` would work too) and this gem:
|
305
334
|
|
306
335
|
[source,ruby]
|
307
336
|
----
|
@@ -467,6 +496,7 @@ When using this gem, along with a container like {dry-container_link}, make sure
|
|
467
496
|
* Use containers that don't have a lot of registered dependencies. If you register too many dependencies, then that means your objects are too complex and need to be simplified further.
|
468
497
|
* Use the `Import` constant to define _what_ is possible to import much like you'd use a `Container` to define your dependencies. Defining what is importable improves performance and should be defined in separate files for improved fuzzy file finding.
|
469
498
|
* Use `**` to forward keyword arguments when defining an initializer which needs to pass injected dependencies upwards.
|
499
|
+
* Prefer `Import#[]` over the use of `Import#public` and/or `Import#protected` as much as a possible since injected dependencies should be private, by default, in order to not break encapsulation. That said, there are times where making them public and/or protected can save you from writing more boilerplate code.
|
470
500
|
|
471
501
|
== Tests
|
472
502
|
|
data/infusible.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "infusible"
|
5
|
-
spec.version = "2.1
|
5
|
+
spec.version = "2.2.1"
|
6
6
|
spec.authors = ["Brooke Kuhlmann"]
|
7
7
|
spec.email = ["brooke@alchemists.io"]
|
8
8
|
spec.homepage = "https://alchemists.io/projects/infusible"
|
@@ -22,7 +22,7 @@ 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 = "
|
25
|
+
spec.required_ruby_version = [">= 3.2", "<= 3.3"]
|
26
26
|
spec.add_dependency "marameters", "~> 2.0"
|
27
27
|
spec.add_dependency "zeitwerk", "~> 2.6"
|
28
28
|
|
data/lib/infusible/actuator.rb
CHANGED
@@ -10,6 +10,10 @@ module Infusible
|
|
10
10
|
|
11
11
|
def [](*configuration) = constructor.new container, *configuration
|
12
12
|
|
13
|
+
def public(*configuration) = constructor.new container, *configuration, scope: __method__
|
14
|
+
|
15
|
+
def protected(*configuration) = constructor.new container, *configuration, scope: __method__
|
16
|
+
|
13
17
|
private
|
14
18
|
|
15
19
|
attr_reader :container, :constructor
|
@@ -4,6 +4,7 @@ require "marameters"
|
|
4
4
|
|
5
5
|
module Infusible
|
6
6
|
# Provides the automatic and complete resolution of all injected dependencies.
|
7
|
+
# :reek:TooManyInstanceVariables
|
7
8
|
class Constructor < Module
|
8
9
|
def self.define_instance_variables target, names, keywords
|
9
10
|
names.each do |name|
|
@@ -15,11 +16,12 @@ module Infusible
|
|
15
16
|
|
16
17
|
private_class_method :define_instance_variables
|
17
18
|
|
18
|
-
def initialize container, *configuration
|
19
|
+
def initialize container, *configuration, scope: :private
|
19
20
|
super()
|
20
21
|
|
21
22
|
@container = container
|
22
23
|
@dependencies = DependencyMap.new(*configuration)
|
24
|
+
@scope = scope
|
23
25
|
@class_module = Class.new(Module).new
|
24
26
|
@instance_module = Class.new(Module).new
|
25
27
|
end
|
@@ -33,7 +35,7 @@ module Infusible
|
|
33
35
|
|
34
36
|
private
|
35
37
|
|
36
|
-
attr_reader :container, :dependencies, :class_module, :instance_module
|
38
|
+
attr_reader :container, :dependencies, :scope, :class_module, :instance_module
|
37
39
|
|
38
40
|
def define klass
|
39
41
|
define_new
|
@@ -89,9 +91,10 @@ module Infusible
|
|
89
91
|
|
90
92
|
def define_readers
|
91
93
|
methods = dependencies.names.map { |name| ":#{name}" }
|
94
|
+
computed_scope = METHOD_SCOPES.include?(scope) ? scope : :private
|
92
95
|
|
93
96
|
instance_module.module_eval <<-READERS, __FILE__, __LINE__ + 1
|
94
|
-
|
97
|
+
#{computed_scope} attr_reader #{methods.join ", "}
|
95
98
|
READERS
|
96
99
|
end
|
97
100
|
end
|
data/lib/infusible.rb
CHANGED
@@ -11,7 +11,11 @@ end
|
|
11
11
|
|
12
12
|
# Main namespace.
|
13
13
|
module Infusible
|
14
|
-
|
14
|
+
METHOD_SCOPES = %i[public protected private].freeze
|
15
|
+
|
16
|
+
def self.loader registry = Zeitwerk::Registry
|
17
|
+
@loader ||= registry.loaders.find { |loader| loader.tag == File.basename(__FILE__, ".rb") }
|
18
|
+
end
|
15
19
|
|
16
20
|
def self.with(container) = Actuator.new container
|
17
21
|
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: 2.1
|
4
|
+
version: 2.2.1
|
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: 2023-
|
38
|
+
date: 2023-11-16 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: marameters
|
@@ -101,16 +101,19 @@ require_paths:
|
|
101
101
|
- lib
|
102
102
|
required_ruby_version: !ruby/object:Gem::Requirement
|
103
103
|
requirements:
|
104
|
-
- - "
|
104
|
+
- - ">="
|
105
105
|
- !ruby/object:Gem::Version
|
106
106
|
version: '3.2'
|
107
|
+
- - "<="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.3'
|
107
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
111
|
requirements:
|
109
112
|
- - ">="
|
110
113
|
- !ruby/object:Gem::Version
|
111
114
|
version: '0'
|
112
115
|
requirements: []
|
113
|
-
rubygems_version: 3.4.
|
116
|
+
rubygems_version: 3.4.22
|
114
117
|
signing_key:
|
115
118
|
specification_version: 4
|
116
119
|
summary: An automated dependency manager and injector.
|
metadata.gz.sig
CHANGED
Binary file
|