infusible 3.0.0 → 3.2.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 +45 -11
- data/infusible.gemspec +1 -1
- data/lib/infusible/constructor.rb +18 -8
- data/lib/infusible/dependency_map.rb +1 -2
- data.tar.gz.sig +1 -1
- 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: 6dc14ad940c37db70d202e1c536e89308476388dafb228c79c10632688ae1f80
|
4
|
+
data.tar.gz: 62a63f85a1bad5e32dcf76945705d87cc2ea705c337f9f047473987992d4bfff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc043d373809d1f649d32d8ed5f0e4f993511c575125f9e02843e7a81f83b5e13bcf15e7ff070866a88a9ffa2b935d1eeb5e7ec961a2f3d9b73b660c6ffce5fe
|
7
|
+
data.tar.gz: 47006245798afea15ba7e440b2c71f8925cbbd649cc0fdb0785612aa7b6716d98bc11cee3bfd720be3c310307ad3a50c7b1d37bf7f3632ffa2cfe4a058176943
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -10,7 +10,7 @@
|
|
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
|
-
|
13
|
+
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:
|
14
14
|
|
15
15
|
[source,ruby]
|
16
16
|
----
|
@@ -73,7 +73,7 @@ require "infusible"
|
|
73
73
|
|
74
74
|
== Usage
|
75
75
|
|
76
|
-
There is basic and advanced usage. We'll start with
|
76
|
+
There is basic and advanced usage. We'll start with the basics and work our to more advanced usage.
|
77
77
|
|
78
78
|
=== Basic
|
79
79
|
|
@@ -81,7 +81,7 @@ This gem requires three steps for proper use:
|
|
81
81
|
|
82
82
|
. A container.
|
83
83
|
. An import constant.
|
84
|
-
.
|
84
|
+
. An object and/or multiple objects for dependencies to be injected into.
|
85
85
|
|
86
86
|
Let's walk through each staring by defining a container of dependencies.
|
87
87
|
|
@@ -238,7 +238,7 @@ class Pinger
|
|
238
238
|
end
|
239
239
|
----
|
240
240
|
|
241
|
-
The above will ensure the logger gets passed upwards to the superclass while remaining accessible by subclass.
|
241
|
+
The above will ensure the logger gets passed upwards to the superclass while remaining accessible by the subclass.
|
242
242
|
|
243
243
|
==== Inheritance
|
244
244
|
|
@@ -289,7 +289,7 @@ end
|
|
289
289
|
|
290
290
|
With the above, the child class will have access to both the `logger` and `http` dependencies.
|
291
291
|
|
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
|
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 the parent/child relationship.
|
293
293
|
|
294
294
|
==== Scopes
|
295
295
|
|
@@ -299,7 +299,7 @@ By default -- and in all of the examples shown so far -- your dependencies are p
|
|
299
299
|
* `include Import.protected(logger)`: Injects a _protected_ logger dependency. Useful with inheritance and a subclass needs access to the dependency.
|
300
300
|
* `include Import.public(:logger)`: Injects a _public_ logger dependency.
|
301
301
|
|
302
|
-
There is no `+#private+` method since `#[]` does this for you and is
|
302
|
+
There is no `+#private+` method since `#[]` does this for you and is _recommended practice_. Use of `+#public+` and `+#protected+` should be used sparingly or not at all if you can avoid it. Here's an example where public, protected, and private dependencies are injected:
|
303
303
|
|
304
304
|
[source,ruby]
|
305
305
|
----
|
@@ -326,9 +326,43 @@ demo.two # NoMethodError: protected method.
|
|
326
326
|
demo.three # NoMethodError: private method.
|
327
327
|
----
|
328
328
|
|
329
|
+
==== Infused Keys
|
330
|
+
|
331
|
+
You have access to the keys of all infused dependencies via the _private_ `infused_keys` method which can be 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
|
+
Example:
|
334
|
+
|
335
|
+
[source,ruby]
|
336
|
+
----
|
337
|
+
module Container
|
338
|
+
extend Dry::Container::Mixin
|
339
|
+
|
340
|
+
register(:one, proc { puts "One" }, call: false)
|
341
|
+
register(:two, proc { puts "Two" }, call: false)
|
342
|
+
end
|
343
|
+
|
344
|
+
Import = Infusible.with Container
|
345
|
+
|
346
|
+
class Demo
|
347
|
+
include Import[:one, :two]
|
348
|
+
|
349
|
+
def call = infused_keys.each { |key| __send__(key).call }
|
350
|
+
end
|
351
|
+
|
352
|
+
Demo.new.call
|
353
|
+
# One
|
354
|
+
# Two
|
355
|
+
----
|
356
|
+
|
357
|
+
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
|
+
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 super class in the hierarchy will have it's own unique array of infused keys depending on what was injected for that object.
|
360
|
+
|
361
|
+
All infused keys are frozen by default.
|
362
|
+
|
329
363
|
=== Tests
|
330
364
|
|
331
|
-
As you architect your implementation, you'll want to test your injected dependencies. You
|
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 built-in for you by only requiring the stub refinement as provided by this gem. For demonstration purposes, I'll assume you are using RSpec but you can adapt for whatever testing framework you are using.
|
332
366
|
|
333
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:
|
334
368
|
|
@@ -381,7 +415,7 @@ RSpec.describe Action do
|
|
381
415
|
end
|
382
416
|
----
|
383
417
|
|
384
|
-
Notice
|
418
|
+
Notice there is little setup required to test the injected dependencies. You only need to use the refinement and define what you want stubbed in your `before` and `after` blocks. That's it!
|
385
419
|
|
386
420
|
While the above works great for a single spec, over time you'll want to reduce duplicated setup by using a shared context. Here's a rewrite of the above spec which significantly reduces duplication when needing to test multiple objects using the same dependencies:
|
387
421
|
|
@@ -453,7 +487,7 @@ bin/console
|
|
453
487
|
|
454
488
|
=== Architecture
|
455
489
|
|
456
|
-
This gem automates a lot of the boilerplate code you'd
|
490
|
+
This gem automates a lot of the boilerplate code you'd manually do by defining your constructor, initializer, and instance variables for you. Normally, when injecting dependencies, you'd do something like this (using the `Pinger` example provided earlier):
|
457
491
|
|
458
492
|
[source,ruby]
|
459
493
|
----
|
@@ -493,10 +527,10 @@ Your constructor, initializer, and instance variables are all there. Only you do
|
|
493
527
|
When using this gem, along with a container like {dry-container_link}, make sure to adhere to the following guidelines:
|
494
528
|
|
495
529
|
* 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.
|
496
|
-
* Use containers that don't have a lot of registered dependencies. If you register too many dependencies,
|
530
|
+
* 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.
|
497
531
|
* 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.
|
498
532
|
* 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
|
533
|
+
* 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 boilerplate code.
|
500
534
|
|
501
535
|
== Tests
|
502
536
|
|
data/infusible.gemspec
CHANGED
@@ -7,6 +7,10 @@ module Infusible
|
|
7
7
|
# :reek:TooManyInstanceVariables
|
8
8
|
class Constructor < Module
|
9
9
|
def self.define_instance_variables target, names, keywords
|
10
|
+
unless target.instance_variable_defined? :@infused_keys
|
11
|
+
target.instance_variable_set :@infused_keys, names
|
12
|
+
end
|
13
|
+
|
10
14
|
names.each do |name|
|
11
15
|
next unless keywords.key?(name) || !target.instance_variable_defined?(:"@#{name}")
|
12
16
|
|
@@ -26,20 +30,25 @@ module Infusible
|
|
26
30
|
@instance_module = Class.new(Module).new
|
27
31
|
end
|
28
32
|
|
29
|
-
def included
|
33
|
+
def included descendant
|
34
|
+
unless descendant.is_a? Class
|
35
|
+
fail TypeError,
|
36
|
+
"Can only infuse a class, invalid object: #{descendant} (#{descendant.class})."
|
37
|
+
end
|
38
|
+
|
30
39
|
super
|
31
|
-
define
|
32
|
-
|
33
|
-
|
40
|
+
define descendant
|
41
|
+
descendant.extend class_module
|
42
|
+
descendant.include instance_module
|
34
43
|
end
|
35
44
|
|
36
45
|
private
|
37
46
|
|
38
47
|
attr_reader :container, :dependencies, :scope, :class_module, :instance_module
|
39
48
|
|
40
|
-
def define
|
49
|
+
def define descendant
|
41
50
|
define_new
|
42
|
-
define_initialize
|
51
|
+
define_initialize descendant
|
43
52
|
define_readers
|
44
53
|
end
|
45
54
|
|
@@ -52,8 +61,8 @@ module Infusible
|
|
52
61
|
end
|
53
62
|
end
|
54
63
|
|
55
|
-
def define_initialize
|
56
|
-
super_parameters = Marameters.of(
|
64
|
+
def define_initialize descendant
|
65
|
+
super_parameters = Marameters.of(descendant, :initialize).map do |instance|
|
57
66
|
break instance unless instance.only_bare_splats?
|
58
67
|
end
|
59
68
|
|
@@ -94,6 +103,7 @@ module Infusible
|
|
94
103
|
computed_scope = METHOD_SCOPES.include?(scope) ? scope : :private
|
95
104
|
|
96
105
|
instance_module.module_eval <<-READERS, __FILE__, __LINE__ + 1
|
106
|
+
attr_reader :infused_keys
|
97
107
|
#{computed_scope} attr_reader #{methods.join ", "}
|
98
108
|
READERS
|
99
109
|
end
|
@@ -11,13 +11,12 @@ module Infusible
|
|
11
11
|
@patterns = patterns
|
12
12
|
@collection = {}
|
13
13
|
|
14
|
-
configuration = configuration.dup
|
15
14
|
aliases = configuration.last.is_a?(Hash) ? configuration.pop : {}
|
16
15
|
|
17
16
|
configuration.each { |identifier| add to_name(identifier), identifier }
|
18
17
|
aliases.each { |name, identifier| add name, identifier }
|
19
18
|
|
20
|
-
@names = collection.keys
|
19
|
+
@names = collection.keys.freeze
|
21
20
|
end
|
22
21
|
|
23
22
|
def to_h = collection
|
data.tar.gz.sig
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
r��K��ߨ�ɨ���wG�`#,�`�ڮ{��[����I�g������
|
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.2.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-
|
38
|
+
date: 2024-02-18 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: marameters
|
@@ -110,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
110
|
- !ruby/object:Gem::Version
|
111
111
|
version: '0'
|
112
112
|
requirements: []
|
113
|
-
rubygems_version: 3.5.
|
113
|
+
rubygems_version: 3.5.6
|
114
114
|
signing_key:
|
115
115
|
specification_version: 4
|
116
116
|
summary: An automated dependency manager and injector.
|
metadata.gz.sig
CHANGED
Binary file
|