inoculate 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e63449f9ee05019e70acf85faafd0f2adf6554704f560de31b66b12c01e54aeb
4
- data.tar.gz: e6975710a5802d37bd4b84de1345dbd10a10a5cf8538096cd8ffea8459997af9
3
+ metadata.gz: 2649bf406a68234451cc147c069a8b9d58ce91d2a1538e728a532ff4abda2180
4
+ data.tar.gz: 3bf808d9e35467647432ed7d74cab98e669a41f7e887e889735c8d583844eaf7
5
5
  SHA512:
6
- metadata.gz: 95e7e8d09278118af12987096c2919419a66b6ea298d6556a3e7df113403e0fa4edd363a1d19172eef8d4739ffdf10b4e8180b19151cdd19361d3c2ccd861200
7
- data.tar.gz: 9dd927feda03995336037f58b86e9c97be8e2bf83f4ac8a101ee67b10e0d40106bdf0f62a138ef046135ef24f759074c289ef61153c8044d02db08b63e4b20be
6
+ metadata.gz: ff27ab41b2f2da2cc6ebf22e434c88b626b9b0c2bf6dab505a9baa1a777faf82c60512a44bb258e19bf0ceb71315b0f0123ab724d6dcb65dfb0468d3d9cf7b35
7
+ data.tar.gz: 23212f8eeb9dc79c159651ebebc93d4ca13ad7623aa2d85622fe8de7b64f993880db5837e51a072f9116bffc169f02030b0676247f1befb254e86023443436ed
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v0.3.0 \[2022-10-11]
2
+
3
+ * Add instance dependency registration.
4
+ * Only allow blocks (and Procs) as dependency factories.
5
+
1
6
  ## v0.2.0 \[2022-10-11]
2
7
 
3
8
  * Build modules at initialization time so that usage is always thread-safe.
data/README.md CHANGED
@@ -24,6 +24,7 @@ It provides several life-cycles and provides dependency access through private a
24
24
  2. [Usage](#usage)
25
25
  1. [Dependency Life Cycles](#dependency-life-cycles)
26
26
  1. [Transient](#transient)
27
+ 2. [Instance](#instance)
27
28
  2. [Renaming the Declaration API](#renaming-the-declaration-api)
28
29
  3. [Hide Your Dependency on Inoculate](#hide-your-dependency-on-inoculate)
29
30
  3. [Installation](#installation)
@@ -109,6 +110,54 @@ Count is: 0
109
110
  => nil
110
111
  ```
111
112
 
113
+ #### Instance
114
+ Instance dependencies are constructed once for each instance of a dependent class.
115
+
116
+ ```ruby
117
+ class Counter
118
+ attr_reader :count
119
+
120
+ def initialize
121
+ @count = 0
122
+ end
123
+
124
+ def inc
125
+ @count += 1
126
+ end
127
+ end
128
+
129
+ Inoculate.initialize do |config|
130
+ config.instance(:counter) { Counter.new }
131
+ end
132
+
133
+ class Example
134
+ include Inoculate::Porter
135
+ inoculate_with :counter
136
+
137
+ def initialize(name)
138
+ @name = name
139
+ end
140
+
141
+ def to_s
142
+ counter.inc
143
+ "[#{@name}] Count is: #{counter.count}"
144
+ end
145
+ end
146
+
147
+ a = Example.new("a")
148
+ b = Example.new("b")
149
+ puts a, a, b
150
+ ```
151
+
152
+ This results in:
153
+
154
+ ```
155
+ [a] Count is: 1
156
+ [a] Count is: 2
157
+ [b] Count is: 1
158
+ => nil
159
+ ```
160
+
112
161
  ### Renaming the Declaration API
113
162
  The `inoculate_with` API is named to avoid immediate collisions with other modules
114
163
  and code you may have. You can use it as-is, or rename it to something you see fit
@@ -13,8 +13,18 @@ module Inoculate
13
13
  # @see Manufacturer#transient
14
14
  #
15
15
  # @since 0.1.0
16
- def transient(name, builder = nil, &block)
17
- manufacturer.transient(name, builder, &block)
16
+ def transient(name, &block)
17
+ manufacturer.transient(name, &block)
18
+ nil
19
+ end
20
+
21
+ # Register an instance dependency.
22
+ # @see Manufacturer#instance
23
+ #
24
+ # @since 0.3.0
25
+ def instance(name, &block)
26
+ manufacturer.instance(name, &block)
27
+ nil
18
28
  end
19
29
 
20
30
  private
@@ -28,49 +28,94 @@ module Inoculate
28
28
  # manufacturer.transient(:sha1_hasher) { Digest::SHA1.new }
29
29
  #
30
30
  # @example With a Proc
31
- # manufacturer.transient(:sha1_hasher, -> { Digest::SHA1.new })
32
- #
33
- # @example With anything Callable
34
- # class HashingBuilder
35
- # def call = Digest::SHA1.new
36
- # end
37
- # manufacturer.transient(:sha1_hasher, HashingBuilder.new)
31
+ # manufacturer.transient(:sha1_hasher, &-> { Digest::SHA1.new })
38
32
  #
39
33
  # @param name [Symbol, #to_sym] the dependency name which will be used to access it
40
- # @param builder [#call] the callable to build the dependency
41
- # @param block [Proc, nil] an alternative builder callable
34
+ # @param block [Block, Proc] a factory method to build the dependency
42
35
  #
43
- # @raise [Errors::RequiresCallable] if no builder or block is provided
36
+ # @raise [Errors::RequiresCallable] if no block is provided
44
37
  # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
45
38
  # or is not a valid attribute name
46
39
  # @raise [Errors::AlreadyRegistered] if the name has been registered previously
47
40
  #
48
41
  # @since 0.1.0
49
- def transient(name, builder = nil, &block)
50
- validate_builder_name name
42
+ def transient(name, &block)
43
+ validate_dependency_name name
51
44
  raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
52
- raise Errors::RequiresCallable unless builder.respond_to?(:call) || block
45
+ raise Errors::RequiresCallable if block.nil?
53
46
 
54
47
  blueprint_name = name.to_sym
55
48
  @registered_blueprints[blueprint_name] = {
56
49
  lifecycle: :transient,
57
- builder: builder || block,
58
- accessor_module: build_module(blueprint_name, :transient, builder || block)
50
+ factory: block,
51
+ accessor_module: build_module(blueprint_name, :transient, block)
52
+ }
53
+ end
54
+
55
+ # Register an instance dependency.
56
+ #
57
+ # An instance dependency gets created once for each instance of a dependent class.
58
+ #
59
+ # @example With a block
60
+ # manufacturer.instance(:sha1_hasher) { Digest::SHA1.new }
61
+ #
62
+ # @example With a Proc
63
+ # manufacturer.instance(:sha1_hasher, &-> { Digest::SHA1.new })
64
+ #
65
+ # @param name [Symbol, #to_sym] the dependency name which will be used to access it
66
+ # @param block [Block, Proc] a factory method to build the dependency
67
+ #
68
+ # @raise [Errors::RequiresCallable] if no block is provided
69
+ # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
70
+ # or is not a valid attribute name
71
+ # @raise [Errors::AlreadyRegistered] if the name has been registered previously
72
+ #
73
+ # @since 0.3.0
74
+ def instance(name, &block)
75
+ validate_dependency_name name
76
+ raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
77
+ raise Errors::RequiresCallable if block.nil?
78
+
79
+ blueprint_name = name.to_sym
80
+ @registered_blueprints[blueprint_name] = {
81
+ lifecycle: :instance,
82
+ factory: block,
83
+ accessor_module: build_module(blueprint_name, :instance, block)
59
84
  }
60
85
  end
61
86
 
62
87
  private
63
88
 
64
- def build_module(name, lifecycle, builder)
89
+ def build_module(name, lifecycle, factory)
65
90
  module_name = "I#{Digest::SHA1.hexdigest(name.to_s)}"
66
- Providers.module_eval do
67
- const_set(module_name, Module.new do
68
- private define_method(name) { builder.call }
69
- end)
91
+ module_body =
92
+ case lifecycle
93
+ when :transient then build_transient(name, factory)
94
+ when :instance then build_instance(name, factory)
95
+ else raise ArgumentError, "Life cycle #{lifecycle} is not valid. Something has gone very wrong."
96
+ end
97
+
98
+ Providers.module_eval { const_set(module_name, module_body) }
99
+ end
100
+
101
+ def build_transient(name, factory)
102
+ Module.new do
103
+ private define_method(name) { factory.call }
104
+ end
105
+ end
106
+
107
+ def build_instance(name, factory)
108
+ cache_variable_name = "@icache_#{Digest::SHA1.hexdigest(name.to_s)}"
109
+ Module.new do
110
+ define_method(name) do
111
+ instance_variable_set(cache_variable_name, factory.call) unless instance_variable_defined?(cache_variable_name)
112
+ instance_variable_get(cache_variable_name)
113
+ end
114
+ private name
70
115
  end
71
116
  end
72
117
 
73
- def validate_builder_name(name)
118
+ def validate_dependency_name(name)
74
119
  raise Errors::InvalidName, "name must be a symbol or convert to one" unless name.respond_to? :to_sym
75
120
  begin
76
121
  Module.new { attr_reader name }
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Inoculate
4
4
  # The library version.
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
data/sig/inoculate.rbs CHANGED
@@ -6,9 +6,9 @@ module Inoculate
6
6
  end
7
7
 
8
8
  type callable = ^() -> untyped
9
- type lifecycle_name = :transient
9
+ type lifecycle_name = :transient | :instance
10
10
  type builder_name = Symbol
11
- type blueprint = { lifecycle: lifecycle_name, builder: callable, accessor_module: Module? }
11
+ type blueprint = { lifecycle: lifecycle_name, factory: callable, accessor_module: Module? }
12
12
 
13
13
  module Errors
14
14
  class Error < StandardError
@@ -32,13 +32,15 @@ module Inoculate
32
32
 
33
33
  class Manufacturer
34
34
  attr_reader registered_blueprints: Hash[builder_name, blueprint]
35
- def transient: (builder_name | _ToSymbol, callable?) ?{ () -> void } -> void
35
+ def transient: (builder_name | _ToSymbol) { () -> void } -> void
36
+ def instance: (builder_name | _ToSymbol) { () -> void } -> void
36
37
  end
37
38
 
38
39
  class Configurer
39
40
  def initialize: (Manufacturer) -> void
40
41
 
41
- def transient: (builder_name | _ToSymbol, callable?) -> void
42
+ def transient: (builder_name | _ToSymbol) { () -> void } -> nil
43
+ def instance: (builder_name | _ToSymbol) { () -> void } -> nil
42
44
 
43
45
  private
44
46
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inoculate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephan Tarulli