inoculate 0.2.0 → 0.4.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: 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.