inoculate 0.3.0 → 0.5.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: 2649bf406a68234451cc147c069a8b9d58ce91d2a1538e728a532ff4abda2180
4
- data.tar.gz: 3bf808d9e35467647432ed7d74cab98e669a41f7e887e889735c8d583844eaf7
3
+ metadata.gz: 7f86297ac65acf1b0193d5afc05cda7cd6970b6ffcd3699f0003256f2fc06b8b
4
+ data.tar.gz: ce435f6820bd36596f50fa23d5bfbd1a018bca2f2926f5d404f04ac0f54a1296
5
5
  SHA512:
6
- metadata.gz: ff27ab41b2f2da2cc6ebf22e434c88b626b9b0c2bf6dab505a9baa1a777faf82c60512a44bb258e19bf0ceb71315b0f0123ab724d6dcb65dfb0468d3d9cf7b35
7
- data.tar.gz: 23212f8eeb9dc79c159651ebebc93d4ca13ad7623aa2d85622fe8de7b64f993880db5837e51a072f9116bffc169f02030b0676247f1befb254e86023443436ed
6
+ metadata.gz: 885671688aa9dc16c6bde1f677326049d8c5d53fbaf8300e77c4c86d5ded34719ba1dbee542f3baa29a1d5d6c35e75773564142aab6e159448a564a8f49ca9f0
7
+ data.tar.gz: 2b709302a5cba52f5eff6d8faae9e0d1fbfefe893c20763d6ea4865f113af010993bbf89b15cdfe6c6090a52b36e3fb9d8ff513d16d24a0741b323f7c5aff0c5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## v0.5.0 \[2022-10-13]
2
+
3
+ * Add thread singleton dependency registration.
4
+
5
+ ## v0.4.0 \[2022-10-13]
6
+
7
+ * Add singleton dependency registration.
8
+
1
9
  ## v0.3.0 \[2022-10-11]
2
10
 
3
11
  * Add instance dependency registration.
data/README.md CHANGED
@@ -25,6 +25,7 @@ It provides several life-cycles and provides dependency access through private a
25
25
  1. [Dependency Life Cycles](#dependency-life-cycles)
26
26
  1. [Transient](#transient)
27
27
  2. [Instance](#instance)
28
+ 3. [Singleton](#singleton)
28
29
  2. [Renaming the Declaration API](#renaming-the-declaration-api)
29
30
  3. [Hide Your Dependency on Inoculate](#hide-your-dependency-on-inoculate)
30
31
  3. [Installation](#installation)
@@ -100,13 +101,16 @@ class Example
100
101
  end
101
102
  end
102
103
 
103
- puts Example.new
104
+ a = Example.new
105
+ puts a, a, a
104
106
  ```
105
107
 
106
108
  This results in:
107
109
 
108
110
  ```
109
111
  Count is: 0
112
+ Count is: 0
113
+ Count is: 0
110
114
  => nil
111
115
  ```
112
116
 
@@ -158,6 +162,128 @@ This results in:
158
162
  => nil
159
163
  ```
160
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
+
213
+ #### Thread Singleton
214
+ Thread Singleton dependencies are constructed once for any thread or fiber.
215
+
216
+ ```ruby
217
+ class Counter
218
+ attr_reader :count
219
+
220
+ def initialize
221
+ @count = 0
222
+ end
223
+
224
+ def inc
225
+ @count += 1
226
+ end
227
+ end
228
+
229
+ Inoculate.initialize do |config|
230
+ config.thread_singleton(:counter) { Counter.new }
231
+ end
232
+
233
+ class Example
234
+ include Inoculate::Porter
235
+ inoculate_with :counter
236
+
237
+ def initialize(name)
238
+ @name = "Example: #{name}"
239
+ end
240
+
241
+ def to_s
242
+ counter.inc
243
+ "[#{@name}] Count is: #{counter.count}"
244
+ end
245
+ end
246
+
247
+ class AnotherExample
248
+ include Inoculate::Porter
249
+ inoculate_with :counter
250
+
251
+ def initialize(name)
252
+ @name = "AnotherExample: #{name}"
253
+ end
254
+
255
+ def to_s
256
+ 5.times { counter.inc }
257
+ "[#{@name}] Count is: #{counter.count}"
258
+ end
259
+ end
260
+
261
+ threads = %w[a b].map do |tag|
262
+ Thread.new(tag) do |t|
263
+ e = Example.new(t)
264
+ a = AnotherExample.new(t)
265
+ puts e, e, a, a
266
+ end
267
+ end
268
+
269
+ threads.each(&:join)
270
+ ```
271
+
272
+ This results in:
273
+
274
+ ```
275
+ [Example: a] Count is: 1
276
+ [Example: b] Count is: 1
277
+ [Example: b] Count is: 2
278
+ [Example: a] Count is: 2
279
+ [AnotherExample: b] Count is: 7
280
+ [AnotherExample: a] Count is: 7
281
+ [AnotherExample: b] Count is: 12
282
+ [AnotherExample: a] Count is: 12
283
+ => [#<Thread:0x000000010d703c68 (irb):177 dead>, #<Thread:0x000000010d703b50 (irb):177 dead>]
284
+ ```
285
+
286
+
161
287
  ### Renaming the Declaration API
162
288
  The `inoculate_with` API is named to avoid immediate collisions with other modules
163
289
  and code you may have. You can use it as-is, or rename it to something you see fit
@@ -27,6 +27,24 @@ module Inoculate
27
27
  nil
28
28
  end
29
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
37
+ end
38
+
39
+ # Register a thread singleton dependency.
40
+ # @see Manufacturer#thread_singleton
41
+ #
42
+ # @since 0.5.0
43
+ def thread_singleton(name, &block)
44
+ manufacturer.thread_singleton(name, &block)
45
+ nil
46
+ end
47
+
30
48
  private
31
49
 
32
50
  attr_reader :manufacturer
@@ -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
@@ -40,16 +39,7 @@ module Inoculate
40
39
  #
41
40
  # @since 0.1.0
42
41
  def transient(name, &block)
43
- validate_dependency_name name
44
- raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
45
- raise Errors::RequiresCallable if block.nil?
46
-
47
- blueprint_name = name.to_sym
48
- @registered_blueprints[blueprint_name] = {
49
- lifecycle: :transient,
50
- factory: block,
51
- accessor_module: build_module(blueprint_name, :transient, block)
52
- }
42
+ register_blueprint(name, :transient, &block)
53
43
  end
54
44
 
55
45
  # Register an instance dependency.
@@ -72,26 +62,78 @@ module Inoculate
72
62
  #
73
63
  # @since 0.3.0
74
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
+ # Register a thread singleton dependency.
92
+ #
93
+ # A thread singleton dependency gets created once per thread or fiber.
94
+ #
95
+ # @example With a block
96
+ # manufacturer.thread_singleton(:sha1_hasher) { Digest::SHA1.new }
97
+ #
98
+ # @example With a Proc
99
+ # manufacturer.thread_singleton(:sha1_hasher, &-> { Digest::SHA1.new })
100
+ #
101
+ # @param name [Symbol, #to_sym] the dependency name which will be used to access it
102
+ # @param block [Block, Proc] a factory method to build the dependency
103
+ #
104
+ # @raise [Errors::RequiresCallable] if no block is provided
105
+ # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
106
+ # or is not a valid attribute name
107
+ # @raise [Errors::AlreadyRegistered] if the name has been registered previously
108
+ #
109
+ # @since 0.5.0
110
+ def thread_singleton(name, &block)
111
+ register_blueprint(name, :thread_singleton, &block)
112
+ end
113
+
114
+ private
115
+
116
+ def register_blueprint(name, lifecycle, &block)
75
117
  validate_dependency_name name
76
118
  raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
77
119
  raise Errors::RequiresCallable if block.nil?
78
120
 
79
121
  blueprint_name = name.to_sym
80
122
  @registered_blueprints[blueprint_name] = {
81
- lifecycle: :instance,
123
+ lifecycle: lifecycle,
82
124
  factory: block,
83
- accessor_module: build_module(blueprint_name, :instance, block)
125
+ accessor_module: build_module(blueprint_name, lifecycle, block)
84
126
  }
85
127
  end
86
128
 
87
- private
88
-
89
129
  def build_module(name, lifecycle, factory)
90
- module_name = "I#{Digest::SHA1.hexdigest(name.to_s)}"
130
+ module_name = "I#{hash_name(name)}"
91
131
  module_body =
92
132
  case lifecycle
93
133
  when :transient then build_transient(name, factory)
94
134
  when :instance then build_instance(name, factory)
135
+ when :singleton then build_singleton(name, factory)
136
+ when :thread_singleton then build_thread_singleton(name, factory)
95
137
  else raise ArgumentError, "Life cycle #{lifecycle} is not valid. Something has gone very wrong."
96
138
  end
97
139
 
@@ -105,7 +147,7 @@ module Inoculate
105
147
  end
106
148
 
107
149
  def build_instance(name, factory)
108
- cache_variable_name = "@icache_#{Digest::SHA1.hexdigest(name.to_s)}"
150
+ cache_variable_name = "@icache_#{hash_name(name)}"
109
151
  Module.new do
110
152
  define_method(name) do
111
153
  instance_variable_set(cache_variable_name, factory.call) unless instance_variable_defined?(cache_variable_name)
@@ -115,6 +157,24 @@ module Inoculate
115
157
  end
116
158
  end
117
159
 
160
+ def build_singleton(name, factory)
161
+ cache_variable_name = "@@icache_#{hash_name(name)}"
162
+ Module.new do |mod|
163
+ define_method(name) do
164
+ mod.class_variable_set(cache_variable_name, factory.call) unless mod.class_variable_defined?(cache_variable_name)
165
+ mod.class_variable_get(cache_variable_name)
166
+ end
167
+ private name
168
+ end
169
+ end
170
+
171
+ def build_thread_singleton(name, factory)
172
+ thread_variable_name = "icache_#{hash_name(name)}"
173
+ Module.new do
174
+ private define_method(name) { Thread.current[thread_variable_name] ||= factory.call }
175
+ end
176
+ end
177
+
118
178
  def validate_dependency_name(name)
119
179
  raise Errors::InvalidName, "name must be a symbol or convert to one" unless name.respond_to? :to_sym
120
180
  begin
@@ -123,5 +183,9 @@ module Inoculate
123
183
  raise Errors::InvalidName, "name must be a valid attr_reader"
124
184
  end
125
185
  end
186
+
187
+ def hash_name(name)
188
+ Digest::SHA1.hexdigest(name.to_s)
189
+ end
126
190
  end
127
191
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Inoculate
4
4
  # The library version.
5
- VERSION = "0.3.0"
5
+ VERSION = "0.5.0"
6
6
  end
data/sig/inoculate.rbs CHANGED
@@ -34,6 +34,8 @@ module Inoculate
34
34
  attr_reader registered_blueprints: Hash[builder_name, blueprint]
35
35
  def transient: (builder_name | _ToSymbol) { () -> void } -> void
36
36
  def instance: (builder_name | _ToSymbol) { () -> void } -> void
37
+ def singleton: (builder_name | _ToSymbol) { () -> void } -> void
38
+ def thread_singleton: (builder_name | _ToSymbol) { () -> void } -> void
37
39
  end
38
40
 
39
41
  class Configurer
@@ -41,6 +43,8 @@ module Inoculate
41
43
 
42
44
  def transient: (builder_name | _ToSymbol) { () -> void } -> nil
43
45
  def instance: (builder_name | _ToSymbol) { () -> void } -> nil
46
+ def singleton: (builder_name | _ToSymbol) { () -> void } -> nil
47
+ def thread_singleton: (builder_name | _ToSymbol) { () -> void } -> nil
44
48
 
45
49
  private
46
50
 
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.3.0
4
+ version: 0.5.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.