yep 0.0.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 +7 -0
- data/.rubocop.yml +22 -0
- data/.ruby-version +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +20 -0
- data/README.md +202 -0
- data/Rakefile +11 -0
- data/lib/yep.rb +4 -0
- data/lib/yep/container.rb +61 -0
- data/lib/yep/inject.rb +26 -0
- data/lib/yep/version.rb +3 -0
- data/test/test_yep.rb +73 -0
- data/yep.gemspec +19 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f21f4ad7229428a808d252aa5a322ea640394d7db976c48a445ddbac5f6ebca9
|
4
|
+
data.tar.gz: a76b70a6723454e34bb2267e5644dec0fbb458228968a7dc85f6334b76469c4f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e254ea0a473f0a6dbfe5e2fe360f79224268efe5b882080cc4545c4f7bc781bab4b29744e90fce878e8d070c39f5aaea6b5deeec40c5993d21b275b3766c22d
|
7
|
+
data.tar.gz: 226abe945d27ca517e781b7d2c65bbc3d12b6f9b0768b83a63cb83b920bde019bd9c33a9e90b47ce362b75a5a05c9d0e56a075277028edf435dc671d5ddbecbb
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.6
|
3
|
+
Metrics/LineLength:
|
4
|
+
Max: 100
|
5
|
+
Style/Documentation:
|
6
|
+
Enabled: false
|
7
|
+
Style/FrozenStringLiteralComment:
|
8
|
+
Enabled: false
|
9
|
+
Layout/MultilineMethodCallIndentation:
|
10
|
+
Enabled: false
|
11
|
+
Metrics/ModuleLength:
|
12
|
+
Exclude:
|
13
|
+
- "**/*test_*.rb"
|
14
|
+
Metrics/BlockLength:
|
15
|
+
Exclude:
|
16
|
+
- "**/*test_*.rb"
|
17
|
+
Lint/UselessComparison:
|
18
|
+
Exclude:
|
19
|
+
- "**/*test_*.rb"
|
20
|
+
Layout/MultilineOperationIndentation:
|
21
|
+
Exclude:
|
22
|
+
- "**/*test_*.rb"
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.0
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
yep (0.0.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
jaro_winkler (1.5.2)
|
11
|
+
parallel (1.14.0)
|
12
|
+
parser (2.6.0.0)
|
13
|
+
ast (~> 2.4.0)
|
14
|
+
powerpack (0.1.2)
|
15
|
+
psych (3.1.0)
|
16
|
+
rainbow (3.0.0)
|
17
|
+
rubocop (0.65.0)
|
18
|
+
jaro_winkler (~> 1.5.1)
|
19
|
+
parallel (~> 1.10)
|
20
|
+
parser (>= 2.5, != 2.5.1.1)
|
21
|
+
powerpack (~> 0.1)
|
22
|
+
psych (>= 3.1.0)
|
23
|
+
rainbow (>= 2.2.2, < 4.0)
|
24
|
+
ruby-progressbar (~> 1.7)
|
25
|
+
unicode-display_width (~> 1.4.0)
|
26
|
+
ruby-progressbar (1.10.0)
|
27
|
+
unicode-display_width (1.4.1)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
rubocop
|
34
|
+
yep!
|
35
|
+
|
36
|
+
BUNDLED WITH
|
37
|
+
2.0.1
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015-2017 yep
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# yep
|
2
|
+
|
3
|
+
A simple, thread-safe dependency injection framework written in ruby.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
From the command line
|
8
|
+
```
|
9
|
+
gem install yep
|
10
|
+
```
|
11
|
+
|
12
|
+
From a Gemfile
|
13
|
+
```
|
14
|
+
source 'http://rubygems.org'
|
15
|
+
|
16
|
+
gem 'yep'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
In the following example we will use a simple convoluted application that
|
22
|
+
queries an external API for some data.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'net/http'
|
26
|
+
require 'json'
|
27
|
+
|
28
|
+
class RandomUserRepository
|
29
|
+
class << self
|
30
|
+
def random
|
31
|
+
response = Net::HTTP.get(URI('https://randomuser.me/api'))
|
32
|
+
JSON.parse(response)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class User
|
40
|
+
attr_accessor :name
|
41
|
+
|
42
|
+
def random
|
43
|
+
users = RandomUserRepository.random
|
44
|
+
self.name = users['results'][0]['name']['first']
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
The problem with the code above is that the `User` class has a hard dependency
|
51
|
+
on `RandomUserRepository`. This has two negative side effects. It would not be
|
52
|
+
simple in a larger application to swap out the `RandomUserRepository` for
|
53
|
+
another underlying repository such as `SqlUserRepository`. It's not simple
|
54
|
+
to swap out the `RandomUserRepository` for testing purposes, maybe for
|
55
|
+
something like `TestUserRepository`.
|
56
|
+
|
57
|
+
So lets have a look at dependency injection with yep to see how this can be
|
58
|
+
solved.
|
59
|
+
|
60
|
+
### Container
|
61
|
+
|
62
|
+
The container will hold dependencies registered under keys which can then later
|
63
|
+
be resolved. Registration of dependencies should happen once for an application.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
require 'yep'
|
67
|
+
|
68
|
+
class App
|
69
|
+
|
70
|
+
def self.boot
|
71
|
+
Yep::Container.add(:repository, RandomUserRepository,
|
72
|
+
Yep::Container::SINGLETON)
|
73
|
+
Yep::Container.add(:user, User, Yep::Container::INSTANCE)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
App.boot
|
78
|
+
```
|
79
|
+
|
80
|
+
During registration we register a class under a key then assign a lifetime to
|
81
|
+
the registered class.
|
82
|
+
|
83
|
+
If `Yep::Container::SINGLETON` is used as a lifetime, every time the dependency
|
84
|
+
is resolved the container will return the same instance of a class.
|
85
|
+
|
86
|
+
If `Yep::Container::INSTANCE` is used as a lifetime, every time the dependency
|
87
|
+
is resolve the container will return a new instance of a class.
|
88
|
+
|
89
|
+
### Injector
|
90
|
+
|
91
|
+
Now the dependencies are registered to the container lets change the `User`
|
92
|
+
class have the repository injected into the user class.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class User
|
96
|
+
extend Yep::Inject
|
97
|
+
|
98
|
+
attr_accessor :name
|
99
|
+
|
100
|
+
inject(:repository)
|
101
|
+
|
102
|
+
def random
|
103
|
+
users = repository.random
|
104
|
+
self.name = users['results'][0]['name']['first']
|
105
|
+
self
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
By extending `Yep::Inject` you then have access to call the `inject` method.
|
111
|
+
By calling this method it will set an instance variable with the same key as the
|
112
|
+
registered dependency, in this case `repository`. Now by calling
|
113
|
+
`User.new.repository` the `RandomUserRepository` will be returned from the
|
114
|
+
container.
|
115
|
+
|
116
|
+
If at anytime you wanted to swap out and use a different repository, as long
|
117
|
+
as it has the same method signatures and return the same data structures, you
|
118
|
+
can just change the dependency on the container.
|
119
|
+
|
120
|
+
Example:
|
121
|
+
|
122
|
+
A new repository that reads from a SQL database.
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
class SqlUserRepository
|
126
|
+
include SomeSqlModule
|
127
|
+
|
128
|
+
class << self
|
129
|
+
def random
|
130
|
+
response = db.read('SELECT * FROM users LIMIT 1 ORDER BY RAND()')
|
131
|
+
JSON.parse(response)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
Change the dependency of repository during boot.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
require 'yep'
|
141
|
+
|
142
|
+
class App
|
143
|
+
|
144
|
+
def self.boot
|
145
|
+
Yep::Container.add(:repository, SqlUserRepository,
|
146
|
+
Yep::Container::SINGLETON)
|
147
|
+
Yep::Container.add(:user, User, Yep::Container::INSTANCE)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
App.boot
|
152
|
+
```
|
153
|
+
|
154
|
+
Now by calling `User.new.repository` the `SqlUerRepository` will be returned
|
155
|
+
from the container.
|
156
|
+
|
157
|
+
*Note:* By calling `Yep::Container.resolve(:repository)` you can
|
158
|
+
programmatically resolve a dependency from the container.
|
159
|
+
|
160
|
+
### Testing
|
161
|
+
|
162
|
+
During testing you may want to mock how the dependences are resolved.
|
163
|
+
|
164
|
+
In this example we mock the Users repository call to return a reliable set of
|
165
|
+
data for testing.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
class FakeUserRepository
|
169
|
+
class << self
|
170
|
+
def random
|
171
|
+
JSON.parse('{ "results": [ { "name": { "first": "FakeName" }}] }')
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
require 'minitest/autorun'
|
179
|
+
|
180
|
+
require 'yep'
|
181
|
+
|
182
|
+
class TestUser < Minitest::Test
|
183
|
+
User.enable_dependency_mocks!
|
184
|
+
|
185
|
+
def test_name_is_set_after_random_is_called
|
186
|
+
user = User.new
|
187
|
+
user.mock(:repository, FakeUserRepository)
|
188
|
+
user.random
|
189
|
+
|
190
|
+
assert user.name == 'FakeName'
|
191
|
+
|
192
|
+
user.unmock(:repository)
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
In this example the underlying repository is swapped out to make sure the call
|
197
|
+
to `random` will return reliable data. After the test finished the repository
|
198
|
+
is then unmocked (returned back to it's original state)
|
199
|
+
|
200
|
+
## Licence
|
201
|
+
|
202
|
+
See `LICENCE` file.
|
data/Rakefile
ADDED
data/lib/yep.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
module Yep
|
4
|
+
class Container
|
5
|
+
module ClassMethods
|
6
|
+
def add(name, clazz, lifetime = Container::INSTANCE)
|
7
|
+
instance.add(name: name, clazz: clazz, lifetime: lifetime)
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolve(name)
|
11
|
+
instance.resolve(name: name)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def instance
|
17
|
+
@instance ||= new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
extend ClassMethods
|
22
|
+
include MonitorMixin
|
23
|
+
|
24
|
+
INSTANCE = :instance
|
25
|
+
SINGLETON = :singleton
|
26
|
+
|
27
|
+
CannotResolveError = Class.new(StandardError)
|
28
|
+
AlreadyAddedError = Class.new(StandardError)
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
super
|
32
|
+
@store ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def add(name:, clazz:, lifetime:)
|
36
|
+
synchronize do
|
37
|
+
raise(AlreadyAddedError, "Depenency with key #{name} already exists") \
|
38
|
+
if store.key?(name)
|
39
|
+
|
40
|
+
store[name] = {
|
41
|
+
class: clazz,
|
42
|
+
lifetime: lifetime
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolve(name:)
|
48
|
+
synchronize do
|
49
|
+
object = store[name]
|
50
|
+
raise(CannotResolveError, "No dependency is mapped to key #{name}") \
|
51
|
+
if object.nil?
|
52
|
+
|
53
|
+
object[:lifetime] == INSTANCE ? object[:class].new : object[:class]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_reader :store
|
60
|
+
end
|
61
|
+
end
|
data/lib/yep/inject.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Yep
|
2
|
+
module Inject
|
3
|
+
def inject(name)
|
4
|
+
variable = "@#{name}"
|
5
|
+
define_method(name) do
|
6
|
+
unless instance_variable_defined?(variable)
|
7
|
+
instance_variable_set(variable, Container.resolve(name))
|
8
|
+
end
|
9
|
+
|
10
|
+
instance_variable_get(variable)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def enable_dependency_mocks!
|
15
|
+
define_method(:mock) do |name, clazz|
|
16
|
+
variable = "@#{name}"
|
17
|
+
instance_variable_set(variable, clazz)
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method(:unmock) do |name|
|
21
|
+
variable = "@#{name}"
|
22
|
+
instance_variable_set(variable, Container.resolve(name))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/yep/version.rb
ADDED
data/test/test_yep.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'yep'
|
4
|
+
|
5
|
+
class TestYep < Minitest::Test
|
6
|
+
Yep::Container.add(:hash, Hash)
|
7
|
+
Yep::Container.add(:string, String)
|
8
|
+
Yep::Container.add(:math, Math, Yep::Container::SINGLETON)
|
9
|
+
|
10
|
+
def test_resolve_instance_always_returns_a_new_object
|
11
|
+
assert Yep::Container.resolve(:hash).object_id !=
|
12
|
+
Yep::Container.resolve(:hash).object_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_resolve_singleton_always_returns_the_same_object
|
16
|
+
assert Yep::Container.resolve(:math).object_id ==
|
17
|
+
Yep::Container.resolve(:math).object_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_resolve_should_raise_error_when_a_dependency_is_not_found
|
21
|
+
assert_raises Yep::Container::CannotResolveError do
|
22
|
+
Yep::Container.resolve(:foo)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_add_cannot_add_the_same_key_more_than_once
|
27
|
+
assert_raises Yep::Container::AlreadyAddedError do
|
28
|
+
Yep::Container.add(:hash, Hash)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Injected
|
33
|
+
extend Yep::Inject
|
34
|
+
|
35
|
+
inject(:math)
|
36
|
+
inject(:string)
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
def instance
|
40
|
+
@instance ||= new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
extend ClassMethods
|
45
|
+
end
|
46
|
+
|
47
|
+
Injected.enable_dependency_mocks!
|
48
|
+
|
49
|
+
def test_injector_injects_dependency
|
50
|
+
assert Injected.instance.math == Math
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_injector_caches_the_injected_dependency
|
54
|
+
assert Injected.instance.string.object_id ==
|
55
|
+
Injected.instance.string.object_id
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_injector_can_be_mocked
|
59
|
+
Injected.instance.mock(:math, Hash)
|
60
|
+
|
61
|
+
assert Injected.instance.math == Hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_injector_can_be_unmocked
|
65
|
+
Injected.instance.mock(:math, Hash)
|
66
|
+
|
67
|
+
assert Injected.instance.math == Hash
|
68
|
+
|
69
|
+
Injected.instance.unmock(:math)
|
70
|
+
|
71
|
+
assert Injected.instance.math == Math
|
72
|
+
end
|
73
|
+
end
|
data/yep.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
|
+
require 'yep/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'yep'
|
6
|
+
s.version = Yep::VERSION
|
7
|
+
s.authors = ['Dan Watson']
|
8
|
+
s.email = ['dan@paz.am']
|
9
|
+
s.homepage = 'https://github.com/dan-watson/yep'
|
10
|
+
s.summary = 'Yep is a dependency injection framework written in ruby'
|
11
|
+
|
12
|
+
s.rubyforge_project = 'yep'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`
|
17
|
+
.split("\n").map { |f| File.basename(f) }
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yep
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Watson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- dan@paz.am
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".rubocop.yml"
|
21
|
+
- ".ruby-version"
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- lib/yep.rb
|
28
|
+
- lib/yep/container.rb
|
29
|
+
- lib/yep/inject.rb
|
30
|
+
- lib/yep/version.rb
|
31
|
+
- test/test_yep.rb
|
32
|
+
- yep.gemspec
|
33
|
+
homepage: https://github.com/dan-watson/yep
|
34
|
+
licenses: []
|
35
|
+
metadata: {}
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubygems_version: 3.0.1
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: Yep is a dependency injection framework written in ruby
|
55
|
+
test_files: []
|