inoculate 0.2.0 → 0.4.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: 7bd8610b9bbde1304734505a64fdf22c8665961e3b02d93171ac4fc6ccf313aa
4
+ data.tar.gz: 66ee174fa4bd02dfd671a39a946a8343da1d63bb88f9be05ee94816d033a5e4e
5
5
  SHA512:
6
- metadata.gz: 95e7e8d09278118af12987096c2919419a66b6ea298d6556a3e7df113403e0fa4edd363a1d19172eef8d4739ffdf10b4e8180b19151cdd19361d3c2ccd861200
7
- data.tar.gz: 9dd927feda03995336037f58b86e9c97be8e2bf83f4ac8a101ee67b10e0d40106bdf0f62a138ef046135ef24f759074c289ef61153c8044d02db08b63e4b20be
6
+ metadata.gz: bbc2655324e68cd2ad977192b81f9cbef0d63b72da91f05521c59eb91f1d58993da68dbcd8d4f6b69be5e5d93708e847f567955f80cb042761e187b64c1bcdbc
7
+ data.tar.gz: ef316f9b6e6dfa9cd42811f8af5082f402d0c03478a280a2728b61ce9c706a9e0b1cd0249921d18818cfa85b28ccf81969a1330744c3d8d19e95e5fd4f73a81c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## v0.4.0 \[Unreleased]
2
+
3
+ * Add singleton dependency registration.
4
+
5
+ ## v0.3.0 \[2022-10-11]
6
+
7
+ * Add instance dependency registration.
8
+ * Only allow blocks (and Procs) as dependency factories.
9
+
1
10
  ## v0.2.0 \[2022-10-11]
2
11
 
3
12
  * Build modules at initialization time so that usage is always thread-safe.
data/README.md CHANGED
@@ -24,6 +24,8 @@ 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)
28
+ 3. [Singleton](#singleton)
27
29
  2. [Renaming the Declaration API](#renaming-the-declaration-api)
28
30
  3. [Hide Your Dependency on Inoculate](#hide-your-dependency-on-inoculate)
29
31
  3. [Installation](#installation)
@@ -99,16 +101,115 @@ class Example
99
101
  end
100
102
  end
101
103
 
102
- puts Example.new
104
+ a = Example.new
105
+ puts a, a, a
103
106
  ```
104
107
 
105
108
  This results in:
106
109
 
107
110
  ```
108
111
  Count is: 0
112
+ Count is: 0
113
+ Count is: 0
109
114
  => nil
110
115
  ```
111
116
 
117
+ #### Instance
118
+ Instance dependencies are constructed once for each instance of a dependent class.
119
+
120
+ ```ruby
121
+ class Counter
122
+ attr_reader :count
123
+
124
+ def initialize
125
+ @count = 0
126
+ end
127
+
128
+ def inc
129
+ @count += 1
130
+ end
131
+ end
132
+
133
+ Inoculate.initialize do |config|
134
+ config.instance(:counter) { Counter.new }
135
+ end
136
+
137
+ class Example
138
+ include Inoculate::Porter
139
+ inoculate_with :counter
140
+
141
+ def initialize(name)
142
+ @name = name
143
+ end
144
+
145
+ def to_s
146
+ counter.inc
147
+ "[#{@name}] Count is: #{counter.count}"
148
+ end
149
+ end
150
+
151
+ a = Example.new("a")
152
+ b = Example.new("b")
153
+ puts a, a, b
154
+ ```
155
+
156
+ This results in:
157
+
158
+ ```
159
+ [a] Count is: 1
160
+ [a] Count is: 2
161
+ [b] Count is: 1
162
+ => nil
163
+ ```
164
+
165
+ #### Singleton
166
+ Singleton dependencies are constructed once.
167
+
168
+ ```ruby
169
+ class Counter
170
+ attr_reader :count
171
+
172
+ def initialize
173
+ @count = 0
174
+ end
175
+
176
+ def inc
177
+ @count += 1
178
+ end
179
+ end
180
+
181
+ Inoculate.initialize do |config|
182
+ config.singleton(:counter) { Counter.new }
183
+ end
184
+
185
+ class Example
186
+ include Inoculate::Porter
187
+ inoculate_with :counter
188
+
189
+ def initialize(name)
190
+ @name = name
191
+ end
192
+
193
+ def to_s
194
+ counter.inc
195
+ "[#{@name}] Count is: #{counter.count}"
196
+ end
197
+ end
198
+
199
+ a = Example.new("a")
200
+ b = Example.new("b")
201
+ puts a, a, b
202
+ ```
203
+
204
+ This results in:
205
+
206
+ ```
207
+ [a] Count is: 1
208
+ [a] Count is: 2
209
+ [b] Count is: 3
210
+ => nil
211
+ ```
212
+
112
213
  ### Renaming the Declaration API
113
214
  The `inoculate_with` API is named to avoid immediate collisions with other modules
114
215
  and code you may have. You can use it as-is, or rename it to something you see fit
@@ -13,8 +13,27 @@ 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
28
+ end
29
+
30
+ # Register a singleton dependency.
31
+ # @see Manufacturer#singleton
32
+ #
33
+ # @since 0.4.0
34
+ def singleton(name, &block)
35
+ manufacturer.singleton(name, &block)
36
+ nil
18
37
  end
19
38
 
20
39
  private
@@ -5,7 +5,6 @@ require "digest"
5
5
  module Inoculate
6
6
  # Registers and builds dependency injection modules.
7
7
  # @todo singleton life cycle
8
- # @todo instance life cycle
9
8
  # @todo thread singleton life cycle
10
9
  #
11
10
  # @since 0.1.0
@@ -28,49 +27,124 @@ module Inoculate
28
27
  # manufacturer.transient(:sha1_hasher) { Digest::SHA1.new }
29
28
  #
30
29
  # @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)
30
+ # manufacturer.transient(:sha1_hasher, &-> { Digest::SHA1.new })
38
31
  #
39
32
  # @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
33
+ # @param block [Block, Proc] a factory method to build the dependency
42
34
  #
43
- # @raise [Errors::RequiresCallable] if no builder or block is provided
35
+ # @raise [Errors::RequiresCallable] if no block is provided
44
36
  # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
45
37
  # or is not a valid attribute name
46
38
  # @raise [Errors::AlreadyRegistered] if the name has been registered previously
47
39
  #
48
40
  # @since 0.1.0
49
- def transient(name, builder = nil, &block)
50
- validate_builder_name name
41
+ def transient(name, &block)
42
+ register_blueprint(name, :transient, &block)
43
+ end
44
+
45
+ # Register an instance dependency.
46
+ #
47
+ # An instance dependency gets created once for each instance of a dependent class.
48
+ #
49
+ # @example With a block
50
+ # manufacturer.instance(:sha1_hasher) { Digest::SHA1.new }
51
+ #
52
+ # @example With a Proc
53
+ # manufacturer.instance(:sha1_hasher, &-> { Digest::SHA1.new })
54
+ #
55
+ # @param name [Symbol, #to_sym] the dependency name which will be used to access it
56
+ # @param block [Block, Proc] a factory method to build the dependency
57
+ #
58
+ # @raise [Errors::RequiresCallable] if no block is provided
59
+ # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
60
+ # or is not a valid attribute name
61
+ # @raise [Errors::AlreadyRegistered] if the name has been registered previously
62
+ #
63
+ # @since 0.3.0
64
+ def instance(name, &block)
65
+ register_blueprint(name, :instance, &block)
66
+ end
67
+
68
+ # Register a singleton dependency.
69
+ #
70
+ # A singleton dependency gets created once.
71
+ #
72
+ # @example With a block
73
+ # manufacturer.singleton(:sha1_hasher) { Digest::SHA1.new }
74
+ #
75
+ # @example With a Proc
76
+ # manufacturer.singleton(:sha1_hasher, &-> { Digest::SHA1.new })
77
+ #
78
+ # @param name [Symbol, #to_sym] the dependency name which will be used to access it
79
+ # @param block [Block, Proc] a factory method to build the dependency
80
+ #
81
+ # @raise [Errors::RequiresCallable] if no block is provided
82
+ # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
83
+ # or is not a valid attribute name
84
+ # @raise [Errors::AlreadyRegistered] if the name has been registered previously
85
+ #
86
+ # @since 0.4.0
87
+ def singleton(name, &block)
88
+ register_blueprint(name, :singleton, &block)
89
+ end
90
+
91
+ private
92
+
93
+ def register_blueprint(name, lifecycle, &block)
94
+ validate_dependency_name name
51
95
  raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
52
- raise Errors::RequiresCallable unless builder.respond_to?(:call) || block
96
+ raise Errors::RequiresCallable if block.nil?
53
97
 
54
98
  blueprint_name = name.to_sym
55
99
  @registered_blueprints[blueprint_name] = {
56
- lifecycle: :transient,
57
- builder: builder || block,
58
- accessor_module: build_module(blueprint_name, :transient, builder || block)
100
+ lifecycle: lifecycle,
101
+ factory: block,
102
+ accessor_module: build_module(blueprint_name, lifecycle, block)
59
103
  }
60
104
  end
61
105
 
62
- private
106
+ def build_module(name, lifecycle, factory)
107
+ module_name = "I#{hash_name(name)}"
108
+ module_body =
109
+ case lifecycle
110
+ when :transient then build_transient(name, factory)
111
+ when :instance then build_instance(name, factory)
112
+ when :singleton then build_singleton(name, factory)
113
+ else raise ArgumentError, "Life cycle #{lifecycle} is not valid. Something has gone very wrong."
114
+ end
115
+
116
+ Providers.module_eval { const_set(module_name, module_body) }
117
+ end
118
+
119
+ def build_transient(name, factory)
120
+ Module.new do
121
+ private define_method(name) { factory.call }
122
+ end
123
+ end
63
124
 
64
- def build_module(name, lifecycle, builder)
65
- 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)
125
+ def build_instance(name, factory)
126
+ cache_variable_name = "@icache_#{hash_name(name)}"
127
+ Module.new do
128
+ define_method(name) do
129
+ instance_variable_set(cache_variable_name, factory.call) unless instance_variable_defined?(cache_variable_name)
130
+ instance_variable_get(cache_variable_name)
131
+ end
132
+ private name
70
133
  end
71
134
  end
72
135
 
73
- def validate_builder_name(name)
136
+ def build_singleton(name, factory)
137
+ cache_variable_name = "@@icache_#{hash_name(name)}"
138
+ Module.new do |mod|
139
+ define_method(name) do
140
+ mod.class_variable_set(cache_variable_name, factory.call) unless mod.class_variable_defined?(cache_variable_name)
141
+ mod.class_variable_get(cache_variable_name)
142
+ end
143
+ private name
144
+ end
145
+ end
146
+
147
+ def validate_dependency_name(name)
74
148
  raise Errors::InvalidName, "name must be a symbol or convert to one" unless name.respond_to? :to_sym
75
149
  begin
76
150
  Module.new { attr_reader name }
@@ -78,5 +152,9 @@ module Inoculate
78
152
  raise Errors::InvalidName, "name must be a valid attr_reader"
79
153
  end
80
154
  end
155
+
156
+ def hash_name(name)
157
+ Digest::SHA1.hexdigest(name.to_s)
158
+ end
81
159
  end
82
160
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Inoculate
4
4
  # The library version.
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.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,17 @@ 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
37
+ def singleton: (builder_name | _ToSymbol) { () -> void } -> void
36
38
  end
37
39
 
38
40
  class Configurer
39
41
  def initialize: (Manufacturer) -> void
40
42
 
41
- def transient: (builder_name | _ToSymbol, callable?) -> void
43
+ def transient: (builder_name | _ToSymbol) { () -> void } -> nil
44
+ def instance: (builder_name | _ToSymbol) { () -> void } -> nil
45
+ def singleton: (builder_name | _ToSymbol) { () -> void } -> nil
42
46
 
43
47
  private
44
48
 
metadata CHANGED
@@ -1,14 +1,14 @@
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.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephan Tarulli
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-12 00:00:00.000000000 Z
11
+ date: 2022-10-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Inoculate is a small, thread-safe dependency injection library configured entirely with Ruby.