medicine 0.0.2 → 1.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 +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +1 -5
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/README.md +71 -22
- data/TODO +0 -6
- data/lib/medicine.rb +95 -48
- data/lib/medicine/dependencies.rb +60 -0
- data/lib/medicine/dependency.rb +55 -0
- data/lib/medicine/injections.rb +33 -0
- data/lib/medicine/version.rb +1 -1
- data/spec/initialization_injection_spec.rb +43 -0
- data/spec/medicine/dependencies_spec.rb +109 -0
- data/spec/medicine/dependency_spec.rb +142 -0
- data/spec/medicine/di_spec.rb +11 -0
- data/spec/medicine/injections_spec.rb +83 -0
- data/spec/medicine_spec.rb +46 -87
- data/spec/setter_injection_spec_spec.rb +60 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bd32dce92b989b0d76ecc791f5a2306df67c890
|
4
|
+
data.tar.gz: 0c75aa02a7c6835a7138323800930a8556d885a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a08a91ebe6927ea99a50659ac099f3be1ff27ab0191438c8b9d489e202438b2b4a96aeac6959b074866cfbe8b5b276f35b367a51cb80da2bdfd2b51eeaac1b49
|
7
|
+
data.tar.gz: 5d55b2ee2e909e30f91210bb3b43980b44d2fbc9b3eb99313a39f60bf4151f53d98b4acc4c2e7f9708b0707333f3812ab9ae6e93d5897ca08606e49eac4c2653
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -4,11 +4,17 @@
|
|
4
4
|
[](https://codeclimate.com/github/krisleech/medicine)
|
5
5
|
[](https://travis-ci.org/krisleech/medicine)
|
6
6
|
[](https://coveralls.io/r/krisleech/medicine?branch=master)
|
7
|
+
[](http://inch-ci.org/github/krisleech/medicine)
|
7
8
|
|
8
9
|
Simple Dependency Injection for Ruby
|
9
10
|
|
10
|
-
Find yourself
|
11
|
-
|
11
|
+
Find yourself injecting dependencies via the initalizer or a setter method?
|
12
|
+
|
13
|
+
Medicine makes this declarative.
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Include the Medicine module and declare the dependencies with `dependency`.
|
12
18
|
|
13
19
|
```ruby
|
14
20
|
class CastVote
|
@@ -16,53 +22,96 @@ class CastVote
|
|
16
22
|
|
17
23
|
dependency :votes_repo, default: -> { Vote }
|
18
24
|
|
19
|
-
def call
|
20
|
-
|
25
|
+
def call
|
26
|
+
votes_repo # => Vote
|
21
27
|
end
|
22
28
|
end
|
29
|
+
```
|
30
|
+
|
31
|
+
For each dependency declared a private method is defined which returns the
|
32
|
+
dependency.
|
23
33
|
|
34
|
+
### Without injection
|
24
35
|
|
25
|
-
|
26
|
-
|
36
|
+
```ruby
|
37
|
+
command = CastVote.new
|
27
38
|
```
|
28
39
|
|
29
|
-
In
|
40
|
+
In the above case the `votes_repo` method will return `Vote`.
|
41
|
+
|
42
|
+
If no dependency is injected the default will be used.
|
43
|
+
|
44
|
+
Specifying a default is optional and if a dependency is not injected and
|
45
|
+
there is no default an error will be raised if the dependencies method is
|
46
|
+
invoked.
|
47
|
+
|
48
|
+
### Injecting via initializer
|
30
49
|
|
31
|
-
## Injecting a dependency
|
32
50
|
|
33
51
|
```ruby
|
34
|
-
|
35
|
-
cast_vote = CastVote.new(vote_repo: vote_repo)
|
52
|
+
command = CastVote.new(votes_repo: double)
|
36
53
|
```
|
37
54
|
|
38
|
-
|
39
|
-
|
55
|
+
In the above case `votes_repo` will return the double.
|
56
|
+
|
57
|
+
If you try and inject a dependency which has not been declared an error is
|
58
|
+
raised.
|
59
|
+
|
60
|
+
### Injecting via a setter
|
40
61
|
|
41
62
|
```ruby
|
42
|
-
|
43
|
-
|
44
|
-
@arg2 = arg2
|
45
|
-
super(dependencies)
|
46
|
-
end
|
63
|
+
command = CastVote.new
|
64
|
+
command.inject_depdendency(:vote_repo, double)
|
47
65
|
```
|
48
66
|
|
49
|
-
|
67
|
+
In the above case `votes_repo` will return the double.
|
68
|
+
|
69
|
+
If you try and inject a dependency which has not been declared an error is
|
70
|
+
raised.
|
71
|
+
|
72
|
+
### Required dependencies
|
50
73
|
|
51
74
|
```ruby
|
52
75
|
dependency :vote_repo
|
53
76
|
```
|
54
77
|
|
55
|
-
When no default is specified
|
56
|
-
|
78
|
+
When no default is specified the dependency must be injected via the
|
79
|
+
constructor or setter an otherwise an exception will be raised.
|
57
80
|
|
58
|
-
|
81
|
+
### Default dependencies
|
59
82
|
|
60
83
|
```ruby
|
84
|
+
dependency :vote_repo, default: Vote
|
61
85
|
dependency :vote_repo, default: :vote
|
62
86
|
dependency :vote_repo, default: :Vote
|
63
87
|
dependency :vote_repo, default: 'Vote'
|
64
88
|
dependency :vote_repo, default: -> { Vote }
|
65
89
|
```
|
66
90
|
|
67
|
-
|
91
|
+
All the above examples will expose a method called `vote_repo` which returns the
|
68
92
|
`Vote` class as the default dependency.
|
93
|
+
|
94
|
+
### Already got an initializer?
|
95
|
+
|
96
|
+
If you want to pass arguments other than the dependencies in to the constructor
|
97
|
+
don't forget to invoke `super`:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
def initialize(arg1, arg2, dependencies = {})
|
101
|
+
@arg1 = arg1
|
102
|
+
@arg2 = arg2
|
103
|
+
super(dependencies)
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## Compatibility
|
108
|
+
|
109
|
+
Tested with MRI 2.1+ and Rubinius.
|
110
|
+
|
111
|
+
See the [build status](https://travis-ci.org/krisleech/medicine) for details.
|
112
|
+
|
113
|
+
## Running Specs
|
114
|
+
|
115
|
+
```
|
116
|
+
rspec spec
|
117
|
+
```
|
data/TODO
CHANGED
@@ -1,9 +1,3 @@
|
|
1
1
|
# TODO
|
2
2
|
|
3
|
-
test that zsuper/super is called
|
4
|
-
module populates class with many private instance methods
|
5
|
-
* think about namespacing them somehow, module/double underscore...
|
6
3
|
refactor tests
|
7
|
-
test to make sure state is not shared between objects
|
8
|
-
freeze dependency hash passed to initalize
|
9
|
-
release 0.0.2
|
data/lib/medicine.rb
CHANGED
@@ -1,81 +1,126 @@
|
|
1
1
|
require "medicine/version"
|
2
|
-
require "
|
2
|
+
require "medicine/dependencies"
|
3
|
+
require "medicine/injections"
|
3
4
|
|
4
5
|
module Medicine
|
6
|
+
# returns the {Medicine::DI} module
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class MyCommand
|
10
|
+
# include Medicine.di
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @api public
|
5
14
|
def self.di
|
6
15
|
DI
|
7
16
|
end
|
8
17
|
|
9
18
|
RequiredDependencyError = Class.new(::ArgumentError)
|
19
|
+
DependencyUnknownError = Class.new(::StandardError)
|
20
|
+
NoInjectionError = Class.new(::StandardError)
|
10
21
|
|
11
22
|
module DI
|
12
|
-
|
13
|
-
|
23
|
+
# Injects dependencies
|
24
|
+
#
|
25
|
+
# @param [Array<Object>] args - the last argument must be a Hash
|
26
|
+
#
|
27
|
+
# @return [undefined]
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# register_user = RegisterUser.new(user_repo: double('UserRepo'))
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def initialize(injections = {})
|
34
|
+
@injections = Injections.new
|
35
|
+
injects(injections)
|
36
|
+
super()
|
14
37
|
end
|
15
38
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.class.dependencies.each do |name, options|
|
32
|
-
define_singleton_method name do
|
33
|
-
@dependencies.fetch(name) { resolve_dependency(name) }
|
34
|
-
end
|
35
|
-
self.singleton_class.class_eval { private name }
|
36
|
-
end
|
39
|
+
# Injects a dependency
|
40
|
+
#
|
41
|
+
# @param [Symbol] key
|
42
|
+
# @param [Object] dependency
|
43
|
+
#
|
44
|
+
# @return [self]
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# register_user.inject_dependency(:user_repo, double('UserRepo'))
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def inject_dependency(name, dependency)
|
51
|
+
raise DependencyUnknownError, "#{name} has not been declared as a dependency" unless self.class.dependencies.include?(name)
|
52
|
+
@injections.set(name, dependency)
|
53
|
+
self
|
37
54
|
end
|
38
55
|
|
39
|
-
|
40
|
-
|
56
|
+
# Injects dependencies
|
57
|
+
#
|
58
|
+
# @params [Hash] injections
|
59
|
+
#
|
60
|
+
# @return [self]
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# register_user.injects(user_repo: double, user_mailer: double)
|
64
|
+
#
|
65
|
+
# @api public
|
66
|
+
def injects(injections)
|
67
|
+
injections.each { |name, dependency| inject_dependency(name, dependency) }
|
41
68
|
end
|
42
69
|
|
43
|
-
|
44
|
-
|
70
|
+
# Returns injections
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# register_user.injections
|
74
|
+
#
|
75
|
+
# @return [Injections]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def injections
|
79
|
+
@injections.dup.freeze
|
45
80
|
end
|
46
81
|
|
47
|
-
|
48
|
-
self.class.dependencies.keys.select do |key|
|
49
|
-
!@dependencies.has_key?(key) && !self.class.dependencies.fetch(key).has_key?(:default)
|
50
|
-
end
|
51
|
-
end
|
82
|
+
private
|
52
83
|
|
53
|
-
def
|
54
|
-
|
84
|
+
def self.included(base)
|
85
|
+
base.extend(ClassMethods)
|
55
86
|
end
|
56
87
|
|
57
|
-
def
|
58
|
-
|
59
|
-
when 'String' then
|
60
|
-
Inflecto.constantize(Inflecto.camelize(dependency))
|
61
|
-
when 'Symbol' then
|
62
|
-
typecast_dependency(dependency.to_s)
|
63
|
-
when 'Proc' then
|
64
|
-
dependency.call
|
65
|
-
else
|
66
|
-
dependency
|
67
|
-
end
|
88
|
+
def self.prepended(base)
|
89
|
+
base.extend(ClassMethods)
|
68
90
|
end
|
69
91
|
|
70
92
|
module ClassMethods
|
71
93
|
def dependencies
|
72
|
-
@dependencies ||=
|
94
|
+
@dependencies ||= Dependencies.new
|
73
95
|
end
|
74
96
|
|
75
97
|
private
|
76
98
|
|
99
|
+
# Declare a dependency
|
100
|
+
#
|
101
|
+
# @param [Symbol] name
|
102
|
+
# @param [Hash] options
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# class MyThing
|
106
|
+
# depdendency :user_repo, default: -> { User }
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# @api public
|
77
110
|
def dependency(name, options = {})
|
78
|
-
dependencies
|
111
|
+
dependencies.add(name, options)
|
112
|
+
|
113
|
+
define_method name do
|
114
|
+
@injections.fetch(name) do
|
115
|
+
self.class.dependencies.fetch(name).default do
|
116
|
+
raise NoInjectionError, "Dependency not injected and default not declared for #{name}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private name
|
122
|
+
|
123
|
+
self
|
79
124
|
end
|
80
125
|
|
81
126
|
def inherited(subclass)
|
@@ -83,5 +128,7 @@ module Medicine
|
|
83
128
|
super
|
84
129
|
end
|
85
130
|
end
|
131
|
+
|
132
|
+
private_constant :ClassMethods
|
86
133
|
end
|
87
134
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "inflecto"
|
2
|
+
require "medicine/dependency"
|
3
|
+
|
4
|
+
module Medicine
|
5
|
+
UnknownDependency = Class.new(StandardError)
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
|
9
|
+
class Dependencies
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@dependencies = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def all
|
17
|
+
@dependencies
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
all.each(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def empty?
|
25
|
+
all.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def size
|
29
|
+
all.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](name)
|
33
|
+
find { |dependency| dependency.name == name }
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch(name)
|
37
|
+
self[name] || raise(UnknownDependency, "Dependency #{name} is unknown")
|
38
|
+
end
|
39
|
+
|
40
|
+
def include?(name)
|
41
|
+
!!self[name]
|
42
|
+
end
|
43
|
+
|
44
|
+
def <<(dependency)
|
45
|
+
push(dependency)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add(name, options = {})
|
49
|
+
push(Dependency.new(name, options))
|
50
|
+
end
|
51
|
+
|
52
|
+
def push(dependency)
|
53
|
+
all.push(dependency)
|
54
|
+
end
|
55
|
+
|
56
|
+
def without_default
|
57
|
+
reject(&:default?)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Medicine
|
2
|
+
NoDefaultError = Class.new(StandardError)
|
3
|
+
NoDefault = Class.new.freeze
|
4
|
+
|
5
|
+
class Dependency
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
@name = name
|
10
|
+
@default = options.fetch(:default, NoDefault)
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_name
|
14
|
+
name
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns the default, yields block or raises an error
|
18
|
+
#
|
19
|
+
# FIXME: move block yielding to default_or method.
|
20
|
+
def default
|
21
|
+
if default?
|
22
|
+
typecast(@default)
|
23
|
+
else
|
24
|
+
if block_given?
|
25
|
+
yield
|
26
|
+
else
|
27
|
+
raise NoDefaultError, "No default declared for #{name}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def default?
|
33
|
+
!required?
|
34
|
+
end
|
35
|
+
|
36
|
+
def required?
|
37
|
+
@default == NoDefault
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def typecast(dependency)
|
43
|
+
case dependency.class.name
|
44
|
+
when 'String' then
|
45
|
+
Inflecto.constantize(Inflecto.camelize(dependency))
|
46
|
+
when 'Symbol' then
|
47
|
+
typecast(dependency.to_s)
|
48
|
+
when 'Proc' then
|
49
|
+
dependency.call
|
50
|
+
else
|
51
|
+
dependency
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Medicine
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
|
5
|
+
class Injections
|
6
|
+
def initialize
|
7
|
+
@injections = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch(name, &block)
|
11
|
+
@injections.fetch(name, &block)
|
12
|
+
rescue KeyError
|
13
|
+
raise ArgumentError, "No dependency with name #{name} has been injected."
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
@injections[name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def set(name, dependency)
|
21
|
+
warn "#{name} has already been injected" if include?(name)
|
22
|
+
@injections[name] = dependency
|
23
|
+
end
|
24
|
+
|
25
|
+
def include?(name)
|
26
|
+
@injections.has_key?(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def empty?
|
30
|
+
@injections.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/medicine/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
RSpec.describe 'injection via initializer' do
|
2
|
+
let(:medicated_class) { new_medicated_class }
|
3
|
+
|
4
|
+
def new_medicated_class
|
5
|
+
Class.new do
|
6
|
+
include Medicine.di
|
7
|
+
|
8
|
+
# access to otherwise private method
|
9
|
+
def _vote_repo
|
10
|
+
vote_repo
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
before { medicated_class.class_eval { dependency :vote_repo } }
|
16
|
+
|
17
|
+
context 'when subject is initalized with a hash' do
|
18
|
+
subject { medicated_class.new(vote_repo: :foo) }
|
19
|
+
|
20
|
+
context 'and hash has key for dependency' do
|
21
|
+
it 'provides a private method' do
|
22
|
+
expect(subject.respond_to?(:vote_repo, true)).to be_truthy
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not provide a public method' do
|
26
|
+
expect(subject).not_to respond_to(:vote_repo)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'provides method which returns injected value' do
|
30
|
+
expect(subject._vote_repo).to eq :foo
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not resolve default' do
|
34
|
+
medicated_class.class_eval do
|
35
|
+
dependency :vote_repo, default: -> { DoesNotExist }
|
36
|
+
end
|
37
|
+
|
38
|
+
object = medicated_class.new(vote_repo: 'bar')
|
39
|
+
expect { object._vote_repo.not_to raise_error }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
RSpec.describe Medicine::Dependencies do
|
2
|
+
it 'is a collection' do
|
3
|
+
expect(subject).to be_a(Enumerable)
|
4
|
+
expect(subject).to respond_to(:each)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe '#[]' do
|
8
|
+
context 'when dependency is known' do
|
9
|
+
before { subject.add(:foo) }
|
10
|
+
|
11
|
+
it 'returns a dependency' do
|
12
|
+
expect(subject[:foo]).to an_instance_of(Medicine::Dependency)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when dependency is unknown' do
|
17
|
+
it 'returns nil' do
|
18
|
+
expect(subject[:foo]).to be_nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#fetch' do
|
24
|
+
context 'when dependency is known' do
|
25
|
+
before { subject.add(:foo) }
|
26
|
+
|
27
|
+
it 'returns a dependency' do
|
28
|
+
expect(subject.fetch(:foo)).to an_instance_of(Medicine::Dependency)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when when dependency is unknown' do
|
33
|
+
it 'raise an error' do
|
34
|
+
expect { subject.fetch(:foo) }.to raise_error(Medicine::UnknownDependency, "Dependency foo is unknown")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#include?' do
|
40
|
+
context 'when dependency is known' do
|
41
|
+
before { subject.add(:foo) }
|
42
|
+
|
43
|
+
it 'returns true' do
|
44
|
+
expect(subject.include?(:foo)).to be_truthy
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when when dependency is unknown' do
|
49
|
+
it 'returns false' do
|
50
|
+
expect(subject.include?(:foo)).to be_falsey
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#<<' do
|
56
|
+
it 'appends given dependency' do
|
57
|
+
expect { subject << double }.to change { subject.size }.by(1)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#add' do
|
62
|
+
it 'appends dependency' do
|
63
|
+
expect { subject.add('foo') }.to change { subject.size }.by(1)
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'options' do
|
67
|
+
let(:added_dependency) { subject.first }
|
68
|
+
|
69
|
+
context 'default given' do
|
70
|
+
let(:default) { double }
|
71
|
+
let(:options) { { default: default } }
|
72
|
+
|
73
|
+
before { subject.add('foo', options) }
|
74
|
+
|
75
|
+
it 'sets default' do
|
76
|
+
expect(added_dependency).to be_default
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'default not given' do
|
81
|
+
let(:options) { {} }
|
82
|
+
|
83
|
+
before { subject.add('foo', options) }
|
84
|
+
|
85
|
+
it 'appends dependency without default' do
|
86
|
+
expect(added_dependency).not_to be_default
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#without_defaults' do
|
93
|
+
let(:dependency_with_default) { double('Dependency', default?: true) }
|
94
|
+
let(:dependency_without_default) { double('Dependency', default?: false) }
|
95
|
+
|
96
|
+
it 'returns dependencies without a default' do
|
97
|
+
subject << dependency_without_default
|
98
|
+
subject << dependency_with_default
|
99
|
+
|
100
|
+
expect(subject.without_default).to eq [dependency_without_default]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'initialize' do
|
105
|
+
it 'is empty' do
|
106
|
+
expect(subject).to be_empty
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
RSpec.describe Medicine::Dependency do
|
2
|
+
subject { described_class }
|
3
|
+
|
4
|
+
TOKEN = Class.new
|
5
|
+
|
6
|
+
describe '#initialize' do
|
7
|
+
it 'sets name' do
|
8
|
+
expect(subject.new(TOKEN, {}).name).to eq TOKEN
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'given default option' do
|
12
|
+
let(:dependency) { subject.new('foo', default: TOKEN) }
|
13
|
+
|
14
|
+
it 'sets default to given value' do
|
15
|
+
expect(dependency.default).to eq TOKEN
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'without default option' do
|
20
|
+
let(:dependency) { subject.new('foo') }
|
21
|
+
|
22
|
+
it 'raises an error' do
|
23
|
+
expect { dependency.default }.to raise_error(Medicine::NoDefaultError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#required?' do
|
29
|
+
context 'when has non-nil default' do
|
30
|
+
let(:dependency) { subject.new('foo', default: TOKEN) }
|
31
|
+
|
32
|
+
it 'returns false' do
|
33
|
+
expect(dependency.required?).to be_falsey
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when has nil default' do
|
38
|
+
let(:dependency) { subject.new('foo', default: nil) }
|
39
|
+
|
40
|
+
it 'returns false' do
|
41
|
+
expect(dependency.required?).to be_falsey
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when has no default' do
|
46
|
+
let(:dependency) { subject.new('foo') }
|
47
|
+
|
48
|
+
it 'returns true' do
|
49
|
+
expect(dependency.required?).to be_truthy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#default' do
|
55
|
+
context 'when no default given' do
|
56
|
+
let(:dependency) { subject.new('foo') }
|
57
|
+
|
58
|
+
context 'when block given' do
|
59
|
+
it 'yields the block' do
|
60
|
+
expect { |b| dependency.default(&b) }.to yield_with_no_args
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when no block given' do
|
65
|
+
it 'raise an error' do
|
66
|
+
expect { dependency.default }.to raise_error(Medicine::NoDefaultError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when default given' do
|
72
|
+
let(:dependency) { subject.new('foo', default: default) }
|
73
|
+
|
74
|
+
Foo = Class.new
|
75
|
+
Foos = Class.new
|
76
|
+
|
77
|
+
describe 'when initalized with nil' do
|
78
|
+
let(:default) { nil }
|
79
|
+
|
80
|
+
it 'returns a class' do
|
81
|
+
expect(dependency.default).to eq nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'when initalized with a class' do
|
86
|
+
let(:default) { Foo }
|
87
|
+
|
88
|
+
it 'returns a class' do
|
89
|
+
expect(dependency.default).to eq Foo
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'when initialized with a lambda' do
|
94
|
+
let(:default) { -> { Foo } }
|
95
|
+
|
96
|
+
it 'returns a class' do
|
97
|
+
expect(dependency.default).to eq Foo
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'when initialized with a Proc' do
|
102
|
+
let(:default) { Proc.new { Foo } }
|
103
|
+
|
104
|
+
it 'returns a class' do
|
105
|
+
expect(dependency.default).to eq Foo
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'when initialized with a CamelCase String' do
|
110
|
+
let(:default) { 'Foo' }
|
111
|
+
|
112
|
+
it 'returns a class' do
|
113
|
+
expect(dependency.default).to eq Foo
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'when initialized with a plural CamelCase String' do
|
118
|
+
let(:default) { 'Foos' }
|
119
|
+
|
120
|
+
it 'returns a class' do
|
121
|
+
expect(dependency.default).to eq Foos
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'when initialized with a CamelCase Symbol' do
|
126
|
+
let(:default) { :Foo }
|
127
|
+
|
128
|
+
it 'returns a class' do
|
129
|
+
expect(dependency.default).to eq Foo
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe 'when initialized with a lowercase Symbol' do
|
134
|
+
let(:default) { :foo }
|
135
|
+
|
136
|
+
it 'returns a class' do
|
137
|
+
expect(dependency.default).to eq Foo
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
RSpec.describe Medicine::DI do
|
2
|
+
it 'can be included' do
|
3
|
+
klass = Class.new { include Medicine::DI }
|
4
|
+
expect(klass).to respond_to(:dependencies)
|
5
|
+
end
|
6
|
+
|
7
|
+
it 'can be prepended' do
|
8
|
+
klass = Class.new { prepend Medicine::DI }
|
9
|
+
expect(klass).to respond_to(:dependencies)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
RSpec.describe Medicine::Injections do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
4
|
+
let(:dependency) { double }
|
5
|
+
let(:name) { :name }
|
6
|
+
|
7
|
+
describe '#set' do
|
8
|
+
it 'sets a dependency for name' do
|
9
|
+
subject.set(name, dependency)
|
10
|
+
expect(subject.fetch(name)).to eq dependency
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when dependency has already been set' do
|
14
|
+
it 'issues a warning' do
|
15
|
+
expect(subject).to receive(:warn)
|
16
|
+
subject.set(name, dependency)
|
17
|
+
subject.set(name, dependency)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#fetch' do
|
23
|
+
context 'given a known name' do
|
24
|
+
it 'gets a dependency by name' do
|
25
|
+
subject.set(name, dependency)
|
26
|
+
expect(subject.fetch(name)).to eq dependency
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'given an unknown name' do
|
31
|
+
context 'and no block given' do
|
32
|
+
it 'raise an error' do
|
33
|
+
expect { subject.fetch(:made_up_name) }.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'and block given' do
|
38
|
+
# FIXME: assert block is called
|
39
|
+
it 'does not raise an error' do
|
40
|
+
expect { subject.fetch(:made_up_name) { } }.not_to raise_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#[]' do
|
47
|
+
context 'given a known name' do
|
48
|
+
it 'returns dependency' do
|
49
|
+
subject.set(name, dependency)
|
50
|
+
expect(subject[name]).to eq dependency
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'given an unknown name' do
|
55
|
+
it 'returns nil' do
|
56
|
+
expect(subject[name]).to eq nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#empty?' do
|
62
|
+
context 'when no dependencies' do
|
63
|
+
it 'returns true' do
|
64
|
+
expect(subject.empty?).to be_truthy
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#include?' do
|
70
|
+
context 'given known name' do
|
71
|
+
it 'returns true' do
|
72
|
+
subject.set(name, dependency)
|
73
|
+
expect(subject.include?(name)).to be_truthy
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'given an unknown name' do
|
78
|
+
it 'returns false' do
|
79
|
+
expect(subject.include?(:made_up_name)).to be_falsey
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/spec/medicine_spec.rb
CHANGED
@@ -12,61 +12,35 @@ RSpec.describe 'Medicine' do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
describe 'dependency
|
16
|
-
it '
|
17
|
-
medicated_class.class_eval { dependency :
|
15
|
+
describe 'when dependency declared' do
|
16
|
+
it 'survives inheritence' do
|
17
|
+
medicated_class.class_eval { dependency :vote_repo }
|
18
18
|
|
19
19
|
super_medicated_class = Class.new(medicated_class)
|
20
20
|
expect(super_medicated_class.dependencies).not_to be_empty
|
21
21
|
end
|
22
22
|
|
23
|
-
it '
|
24
|
-
medicated_class.class_eval { dependency :
|
23
|
+
it 'does not leak between unrelated classes' do
|
24
|
+
medicated_class.class_eval { dependency :vote_repo }
|
25
25
|
|
26
26
|
expect(medicated_class.dependencies).not_to be_empty
|
27
27
|
expect(new_medicated_class.dependencies).to be_empty
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
describe 'dependency declared
|
31
|
+
describe 'when dependency declared with no default' do
|
32
32
|
before { medicated_class.class_eval { dependency :vote_repo } }
|
33
33
|
|
34
|
-
context 'and
|
34
|
+
context 'and dependency not injected' do
|
35
35
|
subject { medicated_class.new }
|
36
36
|
|
37
|
-
it 'raises
|
38
|
-
expect { subject }.to raise_error(Medicine::
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
context 'and subject initalized with hash' do
|
43
|
-
subject { medicated_class.new(vote_repo: :foo) }
|
44
|
-
|
45
|
-
context 'and hash has key for dependency' do
|
46
|
-
it 'provides a private method' do
|
47
|
-
expect(subject.respond_to?(:vote_repo, true)).to be_truthy
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'does not provide a public method' do
|
51
|
-
expect(subject).not_to respond_to(:vote_repo)
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'provides method which returns injected value' do
|
55
|
-
expect(subject._vote_repo).to eq :foo
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
context 'and hash has no key for dependency' do
|
60
|
-
subject { medicated_class.new({}) }
|
61
|
-
|
62
|
-
it 'raises exception' do
|
63
|
-
expect { subject }.to raise_error(Medicine::RequiredDependencyError)
|
64
|
-
end
|
37
|
+
it 'raises an error' do
|
38
|
+
expect { subject._vote_repo }.to raise_error(Medicine::NoInjectionError, /Dependency not injected and default not declared/)
|
65
39
|
end
|
66
40
|
end
|
67
41
|
end
|
68
42
|
|
69
|
-
|
43
|
+
describe 'dependency declared with default' do
|
70
44
|
|
71
45
|
subject { medicated_class.new }
|
72
46
|
|
@@ -75,77 +49,62 @@ RSpec.describe 'Medicine' do
|
|
75
49
|
Foos = Class.new unless defined?(Foos)
|
76
50
|
end
|
77
51
|
|
78
|
-
describe '
|
79
|
-
|
80
|
-
describe 'as a class' do
|
81
|
-
before { medicated_class.class_eval { dependency :vote_repo, default: Foo } }
|
52
|
+
describe 'as a class' do
|
53
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: Foo } }
|
82
54
|
|
83
|
-
|
84
|
-
|
85
|
-
end
|
55
|
+
it 'returns a class' do
|
56
|
+
expect(subject._vote_repo).to eq Foo
|
86
57
|
end
|
58
|
+
end
|
87
59
|
|
88
|
-
|
89
|
-
|
60
|
+
describe 'as a lambda' do
|
61
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: -> { Foo } } }
|
90
62
|
|
91
|
-
|
92
|
-
|
93
|
-
end
|
63
|
+
it 'returns a class' do
|
64
|
+
expect(subject._vote_repo).to eq Foo
|
94
65
|
end
|
66
|
+
end
|
95
67
|
|
96
|
-
|
97
|
-
|
68
|
+
describe 'as a Proc' do
|
69
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: Proc.new { Foo } } }
|
98
70
|
|
99
|
-
|
100
|
-
|
101
|
-
end
|
71
|
+
it 'returns a class' do
|
72
|
+
expect(subject._vote_repo).to eq Foo
|
102
73
|
end
|
74
|
+
end
|
103
75
|
|
104
|
-
|
105
|
-
|
76
|
+
describe 'as a CamelCase String' do
|
77
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: 'Foo' } }
|
106
78
|
|
107
|
-
|
108
|
-
|
109
|
-
end
|
79
|
+
it 'returns a class' do
|
80
|
+
expect(subject._vote_repo).to eq Foo
|
110
81
|
end
|
82
|
+
end
|
111
83
|
|
112
|
-
|
113
|
-
|
84
|
+
describe 'as a plural CamelCase String' do
|
85
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: 'Foos' } }
|
114
86
|
|
115
|
-
|
116
|
-
|
117
|
-
end
|
87
|
+
it 'returns a class' do
|
88
|
+
expect(subject._vote_repo).to eq Foos
|
118
89
|
end
|
90
|
+
end
|
119
91
|
|
120
|
-
|
121
|
-
|
92
|
+
describe 'as a CamelCase Symbol' do
|
93
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: :Foo} }
|
122
94
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
95
|
+
it 'returns a class' do
|
96
|
+
expect(subject._vote_repo).to eq Foo
|
126
97
|
end
|
98
|
+
end
|
127
99
|
|
128
|
-
|
129
|
-
|
100
|
+
describe 'as a lowercase Symbol' do
|
101
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: :foo} }
|
130
102
|
|
131
|
-
|
132
|
-
|
133
|
-
end
|
103
|
+
it 'returns a class' do
|
104
|
+
expect(subject._vote_repo).to eq Foo
|
134
105
|
end
|
135
|
-
end # with default option
|
136
|
-
end # initialized with no arguments
|
137
|
-
|
138
|
-
describe 'inclusion of module' do
|
139
|
-
it 'can be included' do
|
140
|
-
klass = Class.new { include Medicine.di }
|
141
|
-
expect(klass).to respond_to(:dependencies)
|
142
106
|
end
|
143
|
-
|
144
|
-
it 'can be prepended' do
|
145
|
-
klass = Class.new { prepend Medicine.di }
|
146
|
-
expect(klass).to respond_to(:dependencies)
|
147
|
-
end
|
148
|
-
end
|
107
|
+
end # with default option
|
149
108
|
|
150
109
|
describe 'object initialization' do
|
151
110
|
it 'zsuper is called' do
|
@@ -162,4 +121,4 @@ RSpec.describe 'Medicine' do
|
|
162
121
|
expect(klass.new.initalize_reached).to be_truthy
|
163
122
|
end
|
164
123
|
end
|
165
|
-
end
|
124
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
RSpec.describe 'Medicine' do
|
2
|
+
let(:medicated_class) { new_medicated_class }
|
3
|
+
|
4
|
+
def new_medicated_class
|
5
|
+
Class.new do
|
6
|
+
include Medicine.di
|
7
|
+
|
8
|
+
# access to otherwise private method
|
9
|
+
def _vote_repo
|
10
|
+
vote_repo
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { medicated_class.new }
|
16
|
+
|
17
|
+
describe '#injections' do
|
18
|
+
it 'starts empty' do
|
19
|
+
expect(subject.injections).to be_empty
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns Injections object' do
|
23
|
+
expect(subject.injections).to be_an_instance_of(Medicine::Injections)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is frozen' do
|
27
|
+
expect(subject.injections).to be_frozen
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#inject_dependency' do
|
32
|
+
it 'is a public method' do
|
33
|
+
expect(subject).to respond_to :inject_dependency
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'injects a dependency' do
|
37
|
+
medicated_class.class_eval { dependency :name }
|
38
|
+
subject.inject_dependency(:name, double)
|
39
|
+
expect(subject.injections).not_to be_empty
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'when dependency has not been declared' do
|
43
|
+
it 'raises an error' do
|
44
|
+
expect { subject.inject_dependency(:name, double) }.to raise_error(Medicine::DependencyUnknownError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#injects' do
|
50
|
+
it 'is a public method' do
|
51
|
+
expect(subject).to respond_to :injects
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'invokes inject for each dependency' do
|
55
|
+
medicated_class.class_eval { dependency :name }
|
56
|
+
expect(subject).to receive(:inject_dependency).twice
|
57
|
+
subject.injects(name: double, other: double)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: medicine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kris Leech
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inflecto
|
@@ -38,14 +38,24 @@ files:
|
|
38
38
|
- ".travis.yml"
|
39
39
|
- CHANGELOG.md
|
40
40
|
- Gemfile
|
41
|
+
- Guardfile
|
41
42
|
- LICENSE.txt
|
42
43
|
- README.md
|
43
44
|
- Rakefile
|
44
45
|
- TODO
|
45
46
|
- lib/medicine.rb
|
47
|
+
- lib/medicine/dependencies.rb
|
48
|
+
- lib/medicine/dependency.rb
|
49
|
+
- lib/medicine/injections.rb
|
46
50
|
- lib/medicine/version.rb
|
47
51
|
- medicine.gemspec
|
52
|
+
- spec/initialization_injection_spec.rb
|
53
|
+
- spec/medicine/dependencies_spec.rb
|
54
|
+
- spec/medicine/dependency_spec.rb
|
55
|
+
- spec/medicine/di_spec.rb
|
56
|
+
- spec/medicine/injections_spec.rb
|
48
57
|
- spec/medicine_spec.rb
|
58
|
+
- spec/setter_injection_spec_spec.rb
|
49
59
|
- spec/spec_helper.rb
|
50
60
|
homepage: https://github.com/krisleech/medicine
|
51
61
|
licenses:
|
@@ -67,11 +77,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
77
|
version: '0'
|
68
78
|
requirements: []
|
69
79
|
rubyforge_project:
|
70
|
-
rubygems_version: 2.
|
80
|
+
rubygems_version: 2.5.1
|
71
81
|
signing_key:
|
72
82
|
specification_version: 4
|
73
83
|
summary: Simple Dependency Injection for Ruby
|
74
84
|
test_files:
|
85
|
+
- spec/initialization_injection_spec.rb
|
86
|
+
- spec/medicine/dependencies_spec.rb
|
87
|
+
- spec/medicine/dependency_spec.rb
|
88
|
+
- spec/medicine/di_spec.rb
|
89
|
+
- spec/medicine/injections_spec.rb
|
75
90
|
- spec/medicine_spec.rb
|
91
|
+
- spec/setter_injection_spec_spec.rb
|
76
92
|
- spec/spec_helper.rb
|
77
93
|
has_rdoc:
|