inoculate 0.3.0 → 0.5.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: 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.