infusible 2.1.0 → 2.2.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: a3f33a8eb48000ff021c1e0e9198de5a40bfa616ba99701ae1c824924e0daf48
4
- data.tar.gz: e7f24eed93d44a0b840e9c6923c703363c8fed79617261127aa406336b0acd77
3
+ metadata.gz: 6b0a71db2b81919eb1e7ce8ce7c88b5fe686e2d02953d75f8101eb87cbdfaee4
4
+ data.tar.gz: 10c36ccf9ed5fa08b218c984d6780335d6abe23a6e7b7d10178eb6d8ab61c28d
5
5
  SHA512:
6
- metadata.gz: c36cb4049506ec6fcd8d42ecd39653ecfd3edf3a971f85837ff924dd69169f044bd846f1688c70694dc1d9050c03cd3667f926a0003195d9f1a90c54b56d5b39
7
- data.tar.gz: 9f9c61a5a6db1d4093685f212634fe9265951cf23c9dccb2f30f3a7d088a3b38c3b05404443f35c8f45537178df2110b19d3533b6f7a58758c07f2126b1eaca3
6
+ metadata.gz: 8f125339b58cbfd5b740d03f310615d889b436de44806f7c3403f99040b8d78339418885067bd60411c4890c4861e35fa0aefd90fa9a1fa66900084ae97cb264
7
+ data.tar.gz: d3107ab5c16b77a85bc07fb67661e343622e6943ebd5ba4729283dd54e1fb7886a9e3a36db373c1f1722bb3e617dd98b2a960760f3525854a62cf66c27e35465
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
- :dry-auto_inject_link: link:https://dry-rb.org/gems/dry-auto_inject[Dry AutoInject]
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 infuses dependencies within your object through the use of advanced dependency injection. Dependency injection -- the _D_ in _SOLID_ design -- is a powerful way to compose complex architectures from small objects which have a single responsibility -- the _S_ in _SOLID_ design.
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 gem is inspired by the {dry-auto_inject_link} gem. There are a few major differences between this gem and the original Dry AutoInject gem which are:
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 using _infusing_ dependencies into your object, you have the ability to define common dependencies that can be injected without having to do the manual setup normally required to define a constructor and set private instance variables.
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
- * Uses a slimmed down architecture with a strong focus on keyword arguments.
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 will demonstrate multiple ways in which to manage the injection of your dependencies.
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 for _infusion_ and is accessible to your class as an HTTP dependency.
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 primitve `Hash` would work too) and this gem:
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.0"
5
+ spec.version = "2.2.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/infusible"
@@ -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
- private attr_reader #{methods.join ", "}
97
+ #{computed_scope} attr_reader #{methods.join ", "}
95
98
  READERS
96
99
  end
97
100
  end
data/lib/infusible.rb CHANGED
@@ -11,6 +11,8 @@ end
11
11
 
12
12
  # Main namespace.
13
13
  module Infusible
14
+ METHOD_SCOPES = %i[public protected private].freeze
15
+
14
16
  def self.loader(registry = Zeitwerk::Registry) = registry.loader_for __FILE__
15
17
 
16
18
  def self.with(container) = Actuator.new container
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.0
4
+ version: 2.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: 2023-10-01 00:00:00.000000000 Z
38
+ date: 2023-10-09 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: marameters
metadata.gz.sig CHANGED
Binary file