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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +102 -1
- data/lib/inoculate/configurer.rb +21 -2
- data/lib/inoculate/manufacturer.rb +103 -25
- data/lib/inoculate/version.rb +1 -1
- data/sig/inoculate.rbs +8 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7bd8610b9bbde1304734505a64fdf22c8665961e3b02d93171ac4fc6ccf313aa
|
4
|
+
data.tar.gz: 66ee174fa4bd02dfd671a39a946a8343da1d63bb88f9be05ee94816d033a5e4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
data/lib/inoculate/configurer.rb
CHANGED
@@ -13,8 +13,27 @@ module Inoculate
|
|
13
13
|
# @see Manufacturer#transient
|
14
14
|
#
|
15
15
|
# @since 0.1.0
|
16
|
-
def transient(name,
|
17
|
-
manufacturer.transient(name,
|
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,
|
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
|
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
|
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,
|
50
|
-
|
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
|
96
|
+
raise Errors::RequiresCallable if block.nil?
|
53
97
|
|
54
98
|
blueprint_name = name.to_sym
|
55
99
|
@registered_blueprints[blueprint_name] = {
|
56
|
-
lifecycle:
|
57
|
-
|
58
|
-
accessor_module: build_module(blueprint_name,
|
100
|
+
lifecycle: lifecycle,
|
101
|
+
factory: block,
|
102
|
+
accessor_module: build_module(blueprint_name, lifecycle, block)
|
59
103
|
}
|
60
104
|
end
|
61
105
|
|
62
|
-
|
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
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
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
|
data/lib/inoculate/version.rb
CHANGED
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,
|
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
|
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
|
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.
|
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-
|
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.
|