inoculate 0.1.0 → 0.3.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: b26bc8e397a1e50bb29e716bd1c4abb531f9267765960b936ce03ba8449ab7e5
4
- data.tar.gz: e09a3d2235a100c7723596614845a4faba198261bbecfe979ebdf3b209b7d325
3
+ metadata.gz: 2649bf406a68234451cc147c069a8b9d58ce91d2a1538e728a532ff4abda2180
4
+ data.tar.gz: 3bf808d9e35467647432ed7d74cab98e669a41f7e887e889735c8d583844eaf7
5
5
  SHA512:
6
- metadata.gz: d6a281590288dec453e6adf338f5b9461160d07ea35c8bdb3914a464630dd76d46ecfe3c2d16099d936e0f8c04159c0e1200cd0f7c65cf426e74a844492be8c1
7
- data.tar.gz: 27c2596ce1a7e2e0c24bdcaf8cb83272a71f0f5e5a3addf8caac8aa082f5b13448f0af66226c45162deb96a6a43cc61d3addf052253aac84ec250002f827ef7d
6
+ metadata.gz: ff27ab41b2f2da2cc6ebf22e434c88b626b9b0c2bf6dab505a9baa1a777faf82c60512a44bb258e19bf0ceb71315b0f0123ab724d6dcb65dfb0468d3d9cf7b35
7
+ data.tar.gz: 23212f8eeb9dc79c159651ebebc93d4ca13ad7623aa2d85622fe8de7b64f993880db5837e51a072f9116bffc169f02030b0676247f1befb254e86023443436ed
data/CHANGELOG.md CHANGED
@@ -1,8 +1,17 @@
1
- ## v0.1.0 — 2022-10-10
1
+ ## v0.3.0 \[2022-10-11]
2
+
3
+ * Add instance dependency registration.
4
+ * Only allow blocks (and Procs) as dependency factories.
5
+
6
+ ## v0.2.0 \[2022-10-11]
7
+
8
+ * Build modules at initialization time so that usage is always thread-safe.
9
+
10
+ ## v0.1.0 \[2022-10-10]
2
11
 
3
12
  * Add transient dependency registration.
4
13
  * Add initialization process for gem.
5
14
 
6
- ## v0.0.0 — 2022-10-05
15
+ ## v0.0.0 \[2022-10-05]
7
16
 
8
17
  * Initial gem setup.
data/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  Inoculate is a small, thread-safe dependency injection library configured entirely with Ruby.
4
4
  It provides several life-cycles and provides dependency access through private accessors.
5
5
 
6
+ [![Gem Version](https://badge.fury.io/rb/inoculate.svg)](https://badge.fury.io/rb/inoculate)
7
+
6
8
  ---
7
9
 
8
10
  ## What's in the box?
@@ -22,6 +24,7 @@ It provides several life-cycles and provides dependency access through private a
22
24
  2. [Usage](#usage)
23
25
  1. [Dependency Life Cycles](#dependency-life-cycles)
24
26
  1. [Transient](#transient)
27
+ 2. [Instance](#instance)
25
28
  2. [Renaming the Declaration API](#renaming-the-declaration-api)
26
29
  3. [Hide Your Dependency on Inoculate](#hide-your-dependency-on-inoculate)
27
30
  3. [Installation](#installation)
@@ -107,6 +110,54 @@ Count is: 0
107
110
  => nil
108
111
  ```
109
112
 
113
+ #### Instance
114
+ Instance dependencies are constructed once for each instance of a dependent class.
115
+
116
+ ```ruby
117
+ class Counter
118
+ attr_reader :count
119
+
120
+ def initialize
121
+ @count = 0
122
+ end
123
+
124
+ def inc
125
+ @count += 1
126
+ end
127
+ end
128
+
129
+ Inoculate.initialize do |config|
130
+ config.instance(:counter) { Counter.new }
131
+ end
132
+
133
+ class Example
134
+ include Inoculate::Porter
135
+ inoculate_with :counter
136
+
137
+ def initialize(name)
138
+ @name = name
139
+ end
140
+
141
+ def to_s
142
+ counter.inc
143
+ "[#{@name}] Count is: #{counter.count}"
144
+ end
145
+ end
146
+
147
+ a = Example.new("a")
148
+ b = Example.new("b")
149
+ puts a, a, b
150
+ ```
151
+
152
+ This results in:
153
+
154
+ ```
155
+ [a] Count is: 1
156
+ [a] Count is: 2
157
+ [b] Count is: 1
158
+ => nil
159
+ ```
160
+
110
161
  ### Renaming the Declaration API
111
162
  The `inoculate_with` API is named to avoid immediate collisions with other modules
112
163
  and code you may have. You can use it as-is, or rename it to something you see fit
@@ -13,8 +13,18 @@ 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
18
28
  end
19
29
 
20
30
  private
@@ -7,8 +7,6 @@
7
7
  module Inoculate
8
8
  # The main configuration entrypoint for Inoculate. Use this to set up named dependencies.
9
9
  #
10
- # @note This method is not thread safe.
11
- #
12
10
  # @example A simple dependency configuration
13
11
  # Inoculate.initialize do |config|
14
12
  # config.transient(:http) { Faraday }
@@ -4,7 +4,6 @@ require "digest"
4
4
 
5
5
  module Inoculate
6
6
  # Registers and builds dependency injection modules.
7
- # @todo building needs to be thread-safe
8
7
  # @todo singleton life cycle
9
8
  # @todo instance life cycle
10
9
  # @todo thread singleton life cycle
@@ -29,58 +28,94 @@ module Inoculate
29
28
  # manufacturer.transient(:sha1_hasher) { Digest::SHA1.new }
30
29
  #
31
30
  # @example With a Proc
32
- # manufacturer.transient(:sha1_hasher, -> { Digest::SHA1.new })
33
- #
34
- # @example With anything Callable
35
- # class HashingBuilder
36
- # def call = Digest::SHA1.new
37
- # end
38
- # manufacturer.transient(:sha1_hasher, HashingBuilder.new)
31
+ # manufacturer.transient(:sha1_hasher, &-> { Digest::SHA1.new })
39
32
  #
40
33
  # @param name [Symbol, #to_sym] the dependency name which will be used to access it
41
- # @param builder [#call] the callable to build the dependency
42
- # @param block [Proc, nil] an alternative builder callable
34
+ # @param block [Block, Proc] a factory method to build the dependency
43
35
  #
44
- # @raise [Errors::RequiresCallable] if no builder or block is provided
36
+ # @raise [Errors::RequiresCallable] if no block is provided
45
37
  # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
46
38
  # or is not a valid attribute name
47
39
  # @raise [Errors::AlreadyRegistered] if the name has been registered previously
48
40
  #
49
41
  # @since 0.1.0
50
- def transient(name, builder = nil, &block)
51
- validate_builder_name name
42
+ def transient(name, &block)
43
+ validate_dependency_name name
52
44
  raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
53
- raise Errors::RequiresCallable unless builder.respond_to?(:call) || block
45
+ raise Errors::RequiresCallable if block.nil?
54
46
 
55
- @registered_blueprints[name.to_sym] = {lifecycle: :transient, builder: builder || block, accessor_module: nil}
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
+ }
56
53
  end
57
54
 
58
- # Build the accessor module associated with a dependency name.
55
+ # Register an instance dependency.
56
+ #
57
+ # An instance dependency gets created once for each instance of a dependent class.
59
58
  #
60
- # @param name [Symbol] the dependency name to build an accessor module for
59
+ # @example With a block
60
+ # manufacturer.instance(:sha1_hasher) { Digest::SHA1.new }
61
61
  #
62
- # @raise [Errors::UnknownName] if the dependency name is not registered
62
+ # @example With a Proc
63
+ # manufacturer.instance(:sha1_hasher, &-> { Digest::SHA1.new })
63
64
  #
64
- # @return [Module] the accessor module for accessing instances of the dependency
65
+ # @param name [Symbol, #to_sym] the dependency name which will be used to access it
66
+ # @param block [Block, Proc] a factory method to build the dependency
65
67
  #
66
- # @since 0.1.0
67
- def build(name)
68
- blueprint = @registered_blueprints[name]
69
- raise Errors::UnknownName if blueprint.nil?
70
- return blueprint[:accessor_module] unless blueprint[:accessor_module].nil?
68
+ # @raise [Errors::RequiresCallable] if no block is provided
69
+ # @raise [Errors::InvalidName] if the name is not a symbol, cannot be converted to a symbol,
70
+ # or is not a valid attribute name
71
+ # @raise [Errors::AlreadyRegistered] if the name has been registered previously
72
+ #
73
+ # @since 0.3.0
74
+ def instance(name, &block)
75
+ validate_dependency_name name
76
+ raise Errors::AlreadyRegistered if @registered_blueprints.has_key? name
77
+ raise Errors::RequiresCallable if block.nil?
71
78
 
79
+ blueprint_name = name.to_sym
80
+ @registered_blueprints[blueprint_name] = {
81
+ lifecycle: :instance,
82
+ factory: block,
83
+ accessor_module: build_module(blueprint_name, :instance, block)
84
+ }
85
+ end
86
+
87
+ private
88
+
89
+ def build_module(name, lifecycle, factory)
72
90
  module_name = "I#{Digest::SHA1.hexdigest(name.to_s)}"
73
- builder = blueprint[:builder]
74
- blueprint[:accessor_module] = Providers.module_eval do
75
- const_set(module_name, Module.new do
76
- private define_method(name) { builder.call }
77
- end)
91
+ module_body =
92
+ case lifecycle
93
+ when :transient then build_transient(name, factory)
94
+ when :instance then build_instance(name, factory)
95
+ else raise ArgumentError, "Life cycle #{lifecycle} is not valid. Something has gone very wrong."
96
+ end
97
+
98
+ Providers.module_eval { const_set(module_name, module_body) }
99
+ end
100
+
101
+ def build_transient(name, factory)
102
+ Module.new do
103
+ private define_method(name) { factory.call }
78
104
  end
79
105
  end
80
106
 
81
- private
107
+ def build_instance(name, factory)
108
+ cache_variable_name = "@icache_#{Digest::SHA1.hexdigest(name.to_s)}"
109
+ Module.new do
110
+ define_method(name) do
111
+ instance_variable_set(cache_variable_name, factory.call) unless instance_variable_defined?(cache_variable_name)
112
+ instance_variable_get(cache_variable_name)
113
+ end
114
+ private name
115
+ end
116
+ end
82
117
 
83
- def validate_builder_name(name)
118
+ def validate_dependency_name(name)
84
119
  raise Errors::InvalidName, "name must be a symbol or convert to one" unless name.respond_to? :to_sym
85
120
  begin
86
121
  Module.new { attr_reader name }
@@ -37,7 +37,10 @@ module Inoculate
37
37
  m = Module.new do
38
38
  define_method(method_name) do |*names|
39
39
  names.each do |name|
40
- include(Inoculate.manufacturer.build(name))
40
+ mod = Inoculate.manufacturer.registered_blueprints.dig(name, :accessor_module)
41
+ raise Errors::UnknownName if mod.nil?
42
+
43
+ include(mod)
41
44
  end
42
45
  end
43
46
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Inoculate
4
4
  # The library version.
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.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,18 +32,15 @@ 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
36
- def build: (builder_name) -> Module
37
-
38
- private
39
-
40
- def validate_builder_name: (builder_name) -> void
35
+ def transient: (builder_name | _ToSymbol) { () -> void } -> void
36
+ def instance: (builder_name | _ToSymbol) { () -> void } -> void
41
37
  end
42
38
 
43
39
  class Configurer
44
40
  def initialize: (Manufacturer) -> void
45
41
 
46
- def transient: (builder_name | _ToSymbol, callable?) -> void
42
+ def transient: (builder_name | _ToSymbol) { () -> void } -> nil
43
+ def instance: (builder_name | _ToSymbol) { () -> void } -> nil
47
44
 
48
45
  private
49
46
 
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.1.0
4
+ version: 0.3.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-10 00:00:00.000000000 Z
11
+ date: 2022-10-12 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.