diftw 0.1.0 → 0.2.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
  SHA1:
3
- metadata.gz: e2ca2e3bab186790bc0cb1330edf1060a0beed6f
4
- data.tar.gz: 90572c316d59095be4846b9894fac598a9d2138e
3
+ metadata.gz: 3d520a70920cae29c12a9439e049b090bdc2d23f
4
+ data.tar.gz: e6c858bbdae7bc242650f6e00497229716590108
5
5
  SHA512:
6
- metadata.gz: 524edb6efd5b288c937e4de007ab255922e44cb92ee07211bacbbb20ca4473497fdb12b564f2ed07b8114520ab7bad81ab86a7a407eeed06565a5f1944c6df79
7
- data.tar.gz: 7cc3dace40aadf417ed602d6e376ab8b8198689bba652591581faa0b7bf58d1bdceeb3b7640ace5169b4ce0e5c68446ae5b7a42260ab2cb6e700617a55a1ea48
6
+ metadata.gz: 111bddaae6e8b85ad20e83d9f6cf79462c9c6c13de250ed15bb6e47c982d88f702190190bf01668c4945ed033c242dd7bf9df66479a69367d6544fdea54e854b
7
+ data.tar.gz: 23d75ade4800dd9c22a436c55044f1e3b8577ddb64b37351626d9c857d968137f9a3e620e8e9ee4bee366a9a257db1d21646fa7b8ac46677687cfd3e92c712fa
data/README.md CHANGED
@@ -1,86 +1,176 @@
1
1
  # DiFtw
2
2
 
3
- Dependency Injection For The Win! A small gem for simple, yet flexible, dependency injection in Ruby.
3
+ Dependency Injection For The Win! A small, yet surprisingly powerful, dependency injection library for Ruby.
4
4
 
5
- Some say you don't need DI in Ruby. Perhaps. Others say you don't need a DI *library* in Ruby. Probably true, but only in the pedantic sense that you don't need a DI library for *any* language. But I'll take a nice, idiomatic DI library over "just manually pass in every dependency to all your constructors!" any day. I couldn't find one, so I wrote this.
5
+ **NOTE** This library is pre-1.0 and under active development. It's perfectly usable, but expect breaking API or behavior changes until 1.0.
6
6
 
7
- ## Basic Use
7
+ ### Why DI in Ruby?
8
8
 
9
- # Create your injector
10
- Injector = DiFtw::Injector.new do
11
- # Register some dependencies
9
+ If your only concern is testing, mocks/stubs and `webmock` might be all you need. But if you're working on a large project that hooks into all kinds of enterprisey services, and those services aren't always available in dev/testing/staging, a dash of DI might be just the thing.
10
+
11
+ ### Features
12
+
13
+ * Dead-simple registration API
14
+ * Lazy injection (by default)
15
+ * Inject into all instances, a single instance, a class, or a module
16
+ * Injects singletons (by default)
17
+ * Uses parent-child injectors for max flexibility
18
+ * Threadsafe, except for registration
19
+
20
+ ## Dead-simple registration API
21
+
22
+ # Create your root injector
23
+ DI = DiFtw::Injector.new do
24
+ # Register some dependencies in here
12
25
  register :foo do
13
26
  OpenStruct.new(message: "Foo")
14
27
  end
15
28
  end
16
29
 
17
- # Or register them like this
18
- Injector.register :bar do
30
+ # Or register them out here
31
+ DI.register :bar do
19
32
  OpenStruct.new(message: "Bar")
20
33
  end
21
34
 
22
- # Or with a lambda
23
- Injector[:baz] = -> { OpenStruct.new(message: "Baz") }
35
+ # Or register them with procs
36
+ DI[:baz] = -> { OpenStruct.new(message: "Baz") }
37
+
38
+ ## Lazy injection (by default)
39
+
40
+ class Widget
41
+ include DI.inject :foo, :bar
42
+ end
43
+
44
+ widget = Widget.new
45
+ # foo isn't actually injected until it's first called
46
+ puts widget.foo.message
47
+ => "Foo"
48
+
49
+ Lazy injection is usually fine. But if it isn't, use `inject!`:
24
50
 
25
- # Inject some dependencies into your class
26
51
  class Widget
27
- include Injector.inject(:foo, :bar)
52
+ include DI.inject :foo, :bar
28
53
 
29
- def initialize(random_arg)
30
- # Unlike most DI, it doesn't hijack your initializer! Because Ruby!!1!
31
- @random_arg = random_arg
54
+ def initialize
55
+ inject!
32
56
  end
33
57
  end
34
-
35
- # Now they're instance methods!
58
+
59
+ # foo and bar are immediately injected
36
60
  widget = Widget.new
37
- puts widget.bar.message
38
- => "Bar"
39
61
 
40
- ## Singletons By Default
62
+ ## Inject into all instances, a single instance, a class, or a module
63
+
64
+ # Inject :baz into all Widget instance objects
65
+ class Widget
66
+ include DI.inject :baz
67
+ end
68
+ puts Widget.new.baz.message
69
+ => 'Baz'
70
+
71
+ # Inject :baz into one specific instance
72
+ x = SomeClass.new
73
+ DI.inject_instance x, :baz
74
+ puts x.baz.message
75
+ => 'Baz'
76
+
77
+ # Inject :baz as a class method
78
+ class SomeClass
79
+ DI.inject_instance self, :baz
80
+ end
81
+ puts SomeClass.baz.message
82
+ => 'Baz'
41
83
 
42
- By default the injector injects singletons. Observe:
84
+ # Inject :baz as a module method
85
+ module SomeModule
86
+ DI.inject_instance self, :baz
87
+ end
88
+ puts SomeModule.baz.message
89
+ => 'Baz'
43
90
 
44
- widget1, widget2 = Widget.new, Widget.new
45
- widget1.bar.object_id == widget1.bar.object_id
46
- => true
47
- widget1.bar.object_id == widget2.bar.object_id
91
+ ## Injects singletons (by default)
92
+
93
+ Widget.new.bar.object_id == Widget.new.bar.object_id
48
94
  => true
49
95
 
50
- If you *don't* want your injector to return singletons (i.e. get a new copy each time you inject), initialize your injector like this:
96
+ If you *don't* want your injector to return singletons (i.e. get a new copy each time you inject something), initialize your injector like this:
51
97
 
52
- Injector = DiFtw::Injector.new(singleton: false)
53
- Injector[:bar] = -> { OpenStruct.new(message: 'Bar') }
98
+ DI = DiFtw::Injector.new(singleton: false)
99
+ DI[:bar] = -> { OpenStruct.new(message: 'Bar') }
54
100
  ...
55
- widget1, widget2 = Widget.new, Widget.new
56
- widget1.bar.object_id == widget1.bar.object_id
57
- => true
58
- widget1.bar.object_id == widget2.bar.object_id
101
+ Widget.new.bar.object_id == Widget.new.bar.object_id
59
102
  => false
60
103
 
61
104
  Accessing injected singletons **is thread safe**. However, registering them is not.
62
105
 
63
- ## Lazy, Nested Dependencies
106
+ ## Parent-Child injectors
107
+
108
+ This is **maybe the coolest part**. Each time you call `inject` (or `inject_instance`) you're creating a fresh, empty child `DiFtw::Injector`. It will recursively look up dependencies through the parent chain until it finds the nearest registration of that dependency.
64
109
 
65
- # Define your class and tell it to inject :ab into instances
110
+ This means you can re-register a dependency on your child injector, and *it* will be injected instead of whatever is registered above it in the chain. Objects using sibling or parent injectors will remain unchanged, as they won't know about this registration override. Perhaps some examples are best.
111
+
112
+ # Create your root injector and register :foo
113
+ DI = DiFtw::Injector.new
114
+ DI[:foo] = -> { 'Foo' }
115
+
116
+ class Widget
117
+ include DI.inject :foo
118
+ end
119
+
66
120
  class Spline
67
- include Injector.inject(:ab)
121
+ include DI.inject :foo
68
122
  end
69
123
 
70
- # Init a Spline instance. Doesn't matter that :ab isn't registered; just don't call spline.ab yet.
71
- spline = Spline.new
72
-
73
- # Register :ab, which uses :a and :b. The order you register them in doesn't matter.
74
- Injector[:ab] = -> { Injector[:a] + Injector[:b] }
75
- Injector[:a] = -> { 'A' }
76
- Injector[:b] = -> { 'B' }
77
-
78
- # The :ab dependency isn't actually injected until .ab is called!
79
- puts spline.ab
80
- => 'AB'
81
-
82
- ## DI in tests
124
+ # Widget and Spline each get a new injector instance
125
+ Widget.injector.object_id != DI.object_id
126
+ => true
127
+ Widget.injector.object_id != Spline.injector.object_id
128
+ => true
129
+
130
+ # Each Widget instance gets a new injector instance. Same for Spline.
131
+ w1 = Widget.new
132
+ w1.injector.object_id != Widget.injector.object_id
133
+ => true
134
+
135
+ w2 = Widget.new
136
+ w1.injector.object_id != w2.injector.object_id
137
+ => true
83
138
 
84
- # Presumably your injector is already initialized.
85
- # Simply re-register the dependencies you need for your tests.
86
- Injector[:foo] = -> { OpenStruct.new(message: 'Test Foo') }
139
+ # But all those child injectors are empty. They'll all resolve :foo
140
+ # to whatever is in DI[:foo]
141
+ Widget.new.foo
142
+ => 'Foo'
143
+ Spline.new.foo
144
+ => 'Foo'
145
+ Widget.new.foo.object_id == Spline.new.foo.object_id
146
+ => true
147
+
148
+ # But we could re-register/override :foo in Spline.injector, and all new
149
+ # Spline instances would resolve :foo differently.
150
+ Spline.injector[:foo] = -> { 'Bar' }
151
+ Spline.new.foo
152
+ => 'Bar'
153
+ # But DI and Widget.injector would be unchanged
154
+ Widget.new.foo
155
+ => 'Foo'
156
+
157
+ # We can go even further and override :foo in just one specific instance of Spline
158
+ # NOTE This only works if you're using lazy injection (the default) AND if you haven't called #foo yet
159
+ s = Spline.new
160
+ s.injector[:foo] = -> { 'Baz' }
161
+ s.foo
162
+ => 'Baz'
163
+ # Other Spline instances will still get their override from Spline.injector
164
+ Spline.new.foo
165
+ => 'Bar'
166
+ # While Widget instances will all still get the original value from DI
167
+ Widget.new.foo
168
+ => 'Foo'
169
+
170
+ ## DI in testing/local dev/staging/etc.
171
+
172
+ To inject different dependencies in these environments, you have several options. You can simply re-register dependencies in your root injector:
173
+
174
+ DI[:foo] = -> { OpenStruct.new(message: 'Test Foo') }
175
+
176
+ And you can use the parent-child injector features described above.
@@ -1,2 +1,3 @@
1
1
  require 'diftw/injector'
2
+ require 'diftw/builder'
2
3
  require 'diftw/version'
@@ -0,0 +1,54 @@
1
+ module DiFtw
2
+ #
3
+ # Module for building various things, like other modules.
4
+ #
5
+ module Builder
6
+ #
7
+ # Builds a new module that, when included in a class, defines instance methods for each dependecy.
8
+ #
9
+ # @param parent_injector [DiFtw::Injector] The parent injector
10
+ # @param dependencies [Symbol] All dependency names you want to inject
11
+ # @return [Module] A module with accessor methods defined for each dependency
12
+ #
13
+ def self.injector_module(parent_injector, dependencies)
14
+ Module.new {
15
+ class << self
16
+ attr_accessor :injector, :_diftw_dependencies
17
+ end
18
+
19
+ def self.included(base)
20
+ di_mod = self
21
+ base.class_eval do
22
+ # Set the injector for the whole class
23
+ class << self; attr_reader :injector; end
24
+ @injector = di_mod.injector
25
+
26
+ # Create a new injector for each instance
27
+ define_method :injector do
28
+ @injector ||= Injector.new(parent: di_mod.injector)
29
+ end
30
+
31
+ # Define a method to eager-inject all dependencies
32
+ define_method :inject! do
33
+ di_mod._diftw_dependencies.each do |dep|
34
+ send dep
35
+ end
36
+ nil
37
+ end
38
+
39
+ # Define instance accessor methods
40
+ di_mod._diftw_dependencies.each do |dep|
41
+ define_method dep do
42
+ var = "@_diftw_#{dep}"
43
+ instance_variable_get(var) || instance_variable_set(var, self.injector[dep])
44
+ end
45
+ end
46
+ end
47
+ end
48
+ }.tap { |mod|
49
+ mod.injector = Injector.new(parent: parent_injector)
50
+ mod._diftw_dependencies = dependencies
51
+ }
52
+ end
53
+ end
54
+ end
@@ -26,6 +26,8 @@ module DiFtw
26
26
  # end
27
27
  #
28
28
  class Injector
29
+ # @return [DiFtw::Injector] The parent injector, if any
30
+ attr_reader :parent
29
31
  # @return [Boolean] If true, the Injector injects singleton objects
30
32
  attr_reader :singleton
31
33
  # @return [Hash] A Hash containing all registered depencies (as procs) keyed by Symbols
@@ -33,7 +35,8 @@ module DiFtw
33
35
  # @return [Hash] A Hash containing a Mutex for each dependency (only if singleton == true)
34
36
  attr_reader :mutexes
35
37
 
36
- private :registry, :mutexes
38
+ protected :registry, :mutexes
39
+ private :parent
37
40
 
38
41
  #
39
42
  # Instantiate a new Injector.
@@ -42,9 +45,11 @@ module DiFtw
42
45
  #
43
46
  # @param singleton [Boolean] If true, each dependency will only be initialized once. When false, it will be initialized each time it's injected.
44
47
  #
45
- def initialize(singleton: true, &registrator)
46
- @registry, @mutexes = {}, {}
47
- @singleton = singleton
48
+ def initialize(singleton: true, parent: nil, &registrator)
49
+ @registry, @parent = {}, parent
50
+ @singleton = parent ? parent.singleton : singleton
51
+ @mutexes = (parent ? parent.mutexes.keys : []).
52
+ inject({}) { |a, name| a[name] = Mutex.new; a }
48
53
  instance_eval &registrator if registrator
49
54
  end
50
55
 
@@ -62,9 +67,11 @@ module DiFtw
62
67
  # @return [Injector] returns the Injector object
63
68
  #
64
69
  def register(name, y = nil, &block)
65
- instance_variable_set "@_singleton_#{name}", nil
66
70
  registry[name] = y || block
67
- mutexes[name] = Mutex.new if singleton
71
+ if singleton
72
+ instance_variable_set "@_singleton_#{name}", nil
73
+ mutexes[name] ||= Mutex.new
74
+ end
68
75
  self
69
76
  end
70
77
 
@@ -92,10 +99,10 @@ module DiFtw
92
99
  if singleton
93
100
  var = "@_singleton_#{name}"
94
101
  instance_variable_get(var) || mutexes[name].synchronize {
95
- instance_variable_get(var) || instance_variable_set(var, registry.fetch(name).call)
102
+ instance_variable_get(var) || instance_variable_set(var, resolve!(name))
96
103
  }
97
104
  else
98
- registry.fetch(name).call
105
+ resolve! name
99
106
  end
100
107
  end
101
108
 
@@ -110,10 +117,7 @@ module DiFtw
110
117
  # @param dependencies [Symbol] All dependency names you want to inject.
111
118
  #
112
119
  def inject(*dependencies)
113
- injector_module.tap do |mod|
114
- mod._diftw_injector = self
115
- mod._diftw_dependencies = dependencies
116
- end
120
+ DiFtw::Builder.injector_module self, dependencies
117
121
  end
118
122
 
119
123
  #
@@ -125,41 +129,25 @@ module DiFtw
125
129
  # @param dependencies [Symbol] All dependency names you want to inject.
126
130
  #
127
131
  def inject_instance(instance, *dependencies)
128
- injector = self
129
- instance.instance_eval do
130
- dependencies.each do |dep|
131
- define_singleton_method dep do
132
- var = "@_diftw_#{dep}"
133
- instance_variable_get(var) || instance_variable_set(var, injector[dep])
134
- end
135
- end
136
- end
132
+ mod = DiFtw::Builder.injector_module self, dependencies
133
+ instance.singleton_class.send :include, mod
137
134
  end
138
135
 
139
- private
136
+ protected
140
137
 
141
138
  #
142
- # Builds a new module that, when included in a class, defines instance methods for each dependecy.
139
+ # Recursively look up a dependency
143
140
  #
144
- # @return a new module
141
+ # @param dependency [Symbol] name of dependency
142
+ # @return [Proc]
145
143
  #
146
- def injector_module
147
- Module.new do
148
- class << self
149
- attr_accessor :_diftw_injector, :_diftw_dependencies
150
- end
151
-
152
- def self.included(base)
153
- di_mod = self
154
- base.class_eval do
155
- di_mod._diftw_dependencies.each do |dep|
156
- define_method dep do
157
- var = "@_diftw_#{dep}"
158
- instance_variable_get(var) || instance_variable_set(var, di_mod._diftw_injector[dep])
159
- end
160
- end
161
- end
162
- end
144
+ def resolve!(dependency)
145
+ if parent.nil?
146
+ registry.fetch(dependency).call
147
+ elsif registry.has_key? dependency
148
+ registry[dependency].call
149
+ else
150
+ parent[dependency]
163
151
  end
164
152
  end
165
153
  end
@@ -1,4 +1,4 @@
1
1
  module DiFtw
2
2
  # Library version
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.2.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diftw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Hollinger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-05 00:00:00.000000000 Z
11
+ date: 2016-07-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A small dependency injection library for Ruby
14
14
  email: jordan.hollinger@gmail.com
@@ -19,6 +19,7 @@ files:
19
19
  - LICENSE
20
20
  - README.md
21
21
  - lib/diftw.rb
22
+ - lib/diftw/builder.rb
22
23
  - lib/diftw/injector.rb
23
24
  - lib/diftw/version.rb
24
25
  homepage: https://github.com/jhollinger/ruby-diftw