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.
@@ -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
@@ -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"
@@ -0,0 +1 @@
1
+ 2.6.0
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'rubocop'
5
+ end
6
+
7
+ gemspec
@@ -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.
@@ -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.
@@ -0,0 +1,11 @@
1
+ require 'rake/testtask'
2
+ require 'rubocop/rake_task'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ RuboCop::RakeTask.new
9
+
10
+ desc 'Run tests'
11
+ task default: :test
@@ -0,0 +1,4 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+
3
+ require 'yep/container'
4
+ require 'yep/inject'
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Yep
2
+ VERSION = '0.0.0'.freeze
3
+ end
@@ -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
@@ -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: []