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 +4 -4
- data/README.md +141 -51
- data/lib/diftw.rb +1 -0
- data/lib/diftw/builder.rb +54 -0
- data/lib/diftw/injector.rb +29 -41
- data/lib/diftw/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d520a70920cae29c12a9439e049b090bdc2d23f
|
4
|
+
data.tar.gz: e6c858bbdae7bc242650f6e00497229716590108
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
3
|
+
Dependency Injection For The Win! A small, yet surprisingly powerful, dependency injection library for Ruby.
|
4
4
|
|
5
|
-
|
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
|
-
|
7
|
+
### Why DI in Ruby?
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
18
|
-
|
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
|
23
|
-
|
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
|
52
|
+
include DI.inject :foo, :bar
|
28
53
|
|
29
|
-
def initialize
|
30
|
-
|
31
|
-
@random_arg = random_arg
|
54
|
+
def initialize
|
55
|
+
inject!
|
32
56
|
end
|
33
57
|
end
|
34
|
-
|
35
|
-
#
|
58
|
+
|
59
|
+
# foo and bar are immediately injected
|
36
60
|
widget = Widget.new
|
37
|
-
puts widget.bar.message
|
38
|
-
=> "Bar"
|
39
61
|
|
40
|
-
##
|
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
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
53
|
-
|
98
|
+
DI = DiFtw::Injector.new(singleton: false)
|
99
|
+
DI[:bar] = -> { OpenStruct.new(message: 'Bar') }
|
54
100
|
...
|
55
|
-
|
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
|
-
##
|
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
|
-
|
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
|
121
|
+
include DI.inject :foo
|
68
122
|
end
|
69
123
|
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
#
|
85
|
-
#
|
86
|
-
|
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.
|
data/lib/diftw.rb
CHANGED
@@ -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
|
data/lib/diftw/injector.rb
CHANGED
@@ -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
|
-
|
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, ®istrator)
|
46
|
-
@registry, @
|
47
|
-
@singleton = singleton
|
48
|
+
def initialize(singleton: true, parent: nil, ®istrator)
|
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 ®istrator 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
|
-
|
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,
|
102
|
+
instance_variable_get(var) || instance_variable_set(var, resolve!(name))
|
96
103
|
}
|
97
104
|
else
|
98
|
-
|
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
|
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
|
-
|
129
|
-
|
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
|
-
|
136
|
+
protected
|
140
137
|
|
141
138
|
#
|
142
|
-
#
|
139
|
+
# Recursively look up a dependency
|
143
140
|
#
|
144
|
-
# @
|
141
|
+
# @param dependency [Symbol] name of dependency
|
142
|
+
# @return [Proc]
|
145
143
|
#
|
146
|
-
def
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
data/lib/diftw/version.rb
CHANGED
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.
|
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-
|
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
|