inoculate 0.1.0 → 0.3.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: 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.