medicine 0.0.1 → 0.0.2
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/.travis.yml +10 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +7 -2
- data/README.md +17 -21
- data/TODO +1 -4
- data/lib/medicine/version.rb +1 -1
- data/lib/medicine.rb +36 -10
- data/medicine.gemspec +2 -0
- data/spec/medicine_spec.rb +128 -53
- data/spec/spec_helper.rb +8 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79bc0ab2a504f8fac7530336253908d4d7a79332
|
4
|
+
data.tar.gz: dbd4d812702841a2182eee8506bcd650144190a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8677b2d572b120218ee4fb4a9812948673d2eff51fc703a973f88029a82b77076fbf6d80fe3e90e00af5964c28c945a8cbdf869422e71703b9d18fd29a1c82b5
|
7
|
+
data.tar.gz: 24a18976d5119c76c0a56f6e115382c87c1786060613bedadaf63ff9a8a3b331b3f54289212e6b593771b3436cbcf3ea559aeaa718196f6f19c87f15377ffa0a
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
## 0.0.2 (13th Feb 2015)
|
6
|
+
|
7
|
+
* refactoring
|
8
|
+
* fix for inheritence
|
9
|
+
* add typecasting for default dependencies
|
10
|
+
* allow late evaluation of default dependency via lambda
|
11
|
+
|
12
|
+
## 0.0.1 (9th Feb 2015)
|
13
|
+
|
14
|
+
* Initial code
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Medicine
|
2
2
|
|
3
|
+
[](http://badge.fury.io/rb/medicine)
|
4
|
+
[](https://codeclimate.com/github/krisleech/medicine)
|
5
|
+
[](https://travis-ci.org/krisleech/medicine)
|
6
|
+
[](https://coveralls.io/r/krisleech/medicine?branch=master)
|
7
|
+
|
3
8
|
Simple Dependency Injection for Ruby
|
4
9
|
|
5
10
|
Find yourself passing dependencies in to the initalizer? Medicine makes this
|
@@ -28,10 +33,17 @@ In this example Medicine adds a private method called `vote_repo` which returns
|
|
28
33
|
```ruby
|
29
34
|
vote_repo = double('VoteRepo')
|
30
35
|
cast_vote = CastVote.new(vote_repo: vote_repo)
|
36
|
+
```
|
31
37
|
|
32
|
-
|
38
|
+
If you want to arguments other than the dependencies in to the constructor
|
39
|
+
don't forget to invoke `super`:
|
33
40
|
|
34
|
-
|
41
|
+
```ruby
|
42
|
+
def initialize(arg1, arg2, dependencies = {})
|
43
|
+
@arg1 = arg1
|
44
|
+
@arg2 = arg2
|
45
|
+
super(dependencies)
|
46
|
+
end
|
35
47
|
```
|
36
48
|
|
37
49
|
## Required dependencies
|
@@ -40,33 +52,17 @@ cast_vote = CastVote.new(arg1, arg2, vote_repo: vote_repo)
|
|
40
52
|
dependency :vote_repo
|
41
53
|
```
|
42
54
|
|
43
|
-
When no default is specified and is not injected an
|
55
|
+
When no default is specified and is not injected an exception will be raised on
|
44
56
|
initialization.
|
45
57
|
|
46
58
|
## Default dependencies
|
47
59
|
|
48
60
|
```ruby
|
61
|
+
dependency :vote_repo, default: :vote
|
49
62
|
dependency :vote_repo, default: :Vote
|
50
63
|
dependency :vote_repo, default: 'Vote'
|
51
64
|
dependency :vote_repo, default: -> { Vote }
|
52
65
|
```
|
53
66
|
|
54
|
-
The above examples will expose a method called `vote_repo` which returns the
|
67
|
+
The above examples will expose a method called `vote_repo` which returns the
|
55
68
|
`Vote` class as the default dependency.
|
56
|
-
|
57
|
-
You could also pass an object which responds to call and accepts one argument,
|
58
|
-
the name of the dependency
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
dependency :vote_repo, default: Repo
|
62
|
-
```
|
63
|
-
|
64
|
-
```ruby
|
65
|
-
class Repo
|
66
|
-
def self.call(name)
|
67
|
-
Kernel.constant_get(name.gsub('_repo', ''))
|
68
|
-
end
|
69
|
-
end
|
70
|
-
```
|
71
|
-
|
72
|
-
In this example the `vote_repo` method will return the `Vote` class.
|
data/TODO
CHANGED
@@ -4,9 +4,6 @@ test that zsuper/super is called
|
|
4
4
|
module populates class with many private instance methods
|
5
5
|
* think about namespacing them somehow, module/double underscore...
|
6
6
|
refactor tests
|
7
|
-
implement remaining examples in README
|
8
|
-
add example of how to call super if overriding initialize
|
9
|
-
see how it works with inheritence
|
10
7
|
test to make sure state is not shared between objects
|
11
8
|
freeze dependency hash passed to initalize
|
12
|
-
release 0.0.
|
9
|
+
release 0.0.2
|
data/lib/medicine/version.rb
CHANGED
data/lib/medicine.rb
CHANGED
@@ -1,23 +1,28 @@
|
|
1
1
|
require "medicine/version"
|
2
|
+
require "inflecto"
|
2
3
|
|
3
4
|
module Medicine
|
4
5
|
def self.di
|
5
6
|
DI
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
-
ArgumentError = Class.new(::ArgumentError)
|
9
|
+
RequiredDependencyError = Class.new(::ArgumentError)
|
10
10
|
|
11
|
+
module DI
|
11
12
|
def self.included(base)
|
12
13
|
base.extend(ClassMethods)
|
13
14
|
end
|
14
15
|
|
16
|
+
def self.prepended(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
|
15
20
|
def initialize(*args)
|
16
21
|
@dependencies = extract_dependencies(args)
|
17
22
|
assert_all_dependencies_met
|
18
23
|
define_dependency_methods
|
19
24
|
|
20
|
-
super
|
25
|
+
super
|
21
26
|
end
|
22
27
|
|
23
28
|
private
|
@@ -27,6 +32,7 @@ module Medicine
|
|
27
32
|
define_singleton_method name do
|
28
33
|
@dependencies.fetch(name) { resolve_dependency(name) }
|
29
34
|
end
|
35
|
+
self.singleton_class.class_eval { private name }
|
30
36
|
end
|
31
37
|
end
|
32
38
|
|
@@ -35,26 +41,46 @@ module Medicine
|
|
35
41
|
end
|
36
42
|
|
37
43
|
def assert_all_dependencies_met
|
38
|
-
raise
|
44
|
+
raise RequiredDependencyError, "pass all required dependencies (#{unmet_dependencies.join(', ')}) in to initialize" unless unmet_dependencies.empty?
|
39
45
|
end
|
40
46
|
|
41
|
-
def
|
42
|
-
self.class.dependencies.keys.
|
43
|
-
|
47
|
+
def unmet_dependencies
|
48
|
+
self.class.dependencies.keys.select do |key|
|
49
|
+
!@dependencies.has_key?(key) && !self.class.dependencies.fetch(key).has_key?(:default)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
47
53
|
def resolve_dependency(name)
|
48
|
-
self.class.dependencies.fetch(name).fetch(:default)
|
54
|
+
typecast_dependency(self.class.dependencies.fetch(name).fetch(:default))
|
55
|
+
end
|
56
|
+
|
57
|
+
def typecast_dependency(dependency)
|
58
|
+
case dependency.class.name
|
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
|
49
68
|
end
|
50
69
|
|
51
70
|
module ClassMethods
|
71
|
+
def dependencies
|
72
|
+
@dependencies ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
52
77
|
def dependency(name, options = {})
|
53
78
|
dependencies[name] = options
|
54
79
|
end
|
55
80
|
|
56
|
-
def
|
57
|
-
@dependencies
|
81
|
+
def inherited(subclass)
|
82
|
+
subclass.instance_variable_set("@dependencies", @dependencies)
|
83
|
+
super
|
58
84
|
end
|
59
85
|
end
|
60
86
|
end
|
data/medicine.gemspec
CHANGED
data/spec/medicine_spec.rb
CHANGED
@@ -1,90 +1,165 @@
|
|
1
1
|
RSpec.describe 'Medicine' do
|
2
|
-
let(:
|
2
|
+
let(:medicated_class) { new_medicated_class }
|
3
|
+
|
4
|
+
def new_medicated_class
|
3
5
|
Class.new do
|
4
6
|
include Medicine.di
|
7
|
+
|
8
|
+
# access to otherwise private method
|
9
|
+
def _vote_repo
|
10
|
+
vote_repo
|
11
|
+
end
|
5
12
|
end
|
6
13
|
end
|
7
14
|
|
8
|
-
describe '
|
15
|
+
describe 'dependency declarations' do
|
16
|
+
it 'survive inheritence' do
|
17
|
+
medicated_class.class_eval { dependency :foobar }
|
9
18
|
|
10
|
-
|
11
|
-
|
19
|
+
super_medicated_class = Class.new(medicated_class)
|
20
|
+
expect(super_medicated_class.dependencies).not_to be_empty
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
23
|
+
it 'do not leak between unrelated classes' do
|
24
|
+
medicated_class.class_eval { dependency :foobar }
|
15
25
|
|
16
|
-
|
17
|
-
|
18
|
-
|
26
|
+
expect(medicated_class.dependencies).not_to be_empty
|
27
|
+
expect(new_medicated_class.dependencies).to be_empty
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'dependency declared without any options' do
|
32
|
+
before { medicated_class.class_eval { dependency :vote_repo } }
|
33
|
+
|
34
|
+
context 'and subject initialized with no arguments' do
|
35
|
+
subject { medicated_class.new }
|
36
|
+
|
37
|
+
it 'raises exception' do
|
38
|
+
expect { subject }.to raise_error(Medicine::RequiredDependencyError)
|
19
39
|
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'and subject initalized with hash' do
|
43
|
+
subject { medicated_class.new(vote_repo: :foo) }
|
20
44
|
|
21
|
-
context '
|
22
|
-
|
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
|
23
49
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
50
|
+
it 'does not provide a public method' do
|
51
|
+
expect(subject).not_to respond_to(:vote_repo)
|
52
|
+
end
|
28
53
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
54
|
+
it 'provides method which returns injected value' do
|
55
|
+
expect(subject._vote_repo).to eq :foo
|
32
56
|
end
|
57
|
+
end
|
33
58
|
|
34
|
-
|
35
|
-
|
59
|
+
context 'and hash has no key for dependency' do
|
60
|
+
subject { medicated_class.new({}) }
|
36
61
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
62
|
+
it 'raises exception' do
|
63
|
+
expect { subject }.to raise_error(Medicine::RequiredDependencyError)
|
40
64
|
end
|
41
65
|
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'and subject initialized with no arguments' do
|
42
70
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
71
|
+
subject { medicated_class.new }
|
72
|
+
|
73
|
+
before do
|
74
|
+
Foo = Class.new unless defined?(Foo)
|
75
|
+
Foos = Class.new unless defined?(Foos)
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'and dependency declared with default option' do
|
79
|
+
|
80
|
+
describe 'as a class' do
|
81
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: Foo } }
|
82
|
+
|
83
|
+
it 'returns a class' do
|
84
|
+
expect(subject._vote_repo).to eq Foo
|
50
85
|
end
|
86
|
+
end
|
51
87
|
|
52
|
-
|
53
|
-
|
88
|
+
describe 'as a lambda' do
|
89
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: -> { Foo } } }
|
54
90
|
|
55
|
-
|
56
|
-
|
57
|
-
end
|
91
|
+
it 'returns a class' do
|
92
|
+
expect(subject._vote_repo).to eq Foo
|
58
93
|
end
|
94
|
+
end
|
59
95
|
|
60
|
-
|
61
|
-
|
96
|
+
describe 'as a Proc' do
|
97
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: Proc.new { Foo } } }
|
62
98
|
|
63
|
-
|
64
|
-
|
65
|
-
|
99
|
+
it 'returns a class' do
|
100
|
+
expect(subject._vote_repo).to eq Foo
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'as a CamelCase String' do
|
105
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: 'Foo' } }
|
106
|
+
|
107
|
+
it 'returns a class' do
|
108
|
+
expect(subject._vote_repo).to eq Foo
|
66
109
|
end
|
67
110
|
end
|
68
|
-
end
|
69
111
|
|
70
|
-
|
71
|
-
|
72
|
-
before { klass.class_eval { dependency :vote_repo, default: 'Object' } }
|
112
|
+
describe 'as a plural CamelCase String' do
|
113
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: 'Foos' } }
|
73
114
|
|
74
|
-
|
115
|
+
it 'returns a class' do
|
116
|
+
expect(subject._vote_repo).to eq Foos
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'as a CamelCase Symbol' do
|
121
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: :Foo} }
|
75
122
|
|
76
|
-
|
123
|
+
it 'returns a class' do
|
124
|
+
expect(subject._vote_repo).to eq Foo
|
125
|
+
end
|
126
|
+
end
|
77
127
|
|
78
|
-
|
79
|
-
|
80
|
-
end
|
128
|
+
describe 'as a lowercase Symbol' do
|
129
|
+
before { medicated_class.class_eval { dependency :vote_repo, default: :foo} }
|
81
130
|
|
82
|
-
|
83
|
-
|
84
|
-
end
|
131
|
+
it 'returns a class' do
|
132
|
+
expect(subject._vote_repo).to eq Foo
|
85
133
|
end
|
86
134
|
end
|
87
135
|
end # with default option
|
136
|
+
end # initialized with no arguments
|
88
137
|
|
89
|
-
|
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
|
+
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
|
149
|
+
|
150
|
+
describe 'object initialization' do
|
151
|
+
it 'zsuper is called' do
|
152
|
+
klass = Class.new do
|
153
|
+
prepend Medicine.di
|
154
|
+
|
155
|
+
attr_reader :initalize_reached
|
156
|
+
|
157
|
+
def initialize(*args)
|
158
|
+
@initalize_reached = true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
expect(klass.new.initalize_reached).to be_truthy
|
163
|
+
end
|
164
|
+
end
|
90
165
|
end # rspec
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: medicine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kris Leech
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
12
|
-
dependencies:
|
11
|
+
date: 2015-02-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: inflecto
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description: Simple Dependency Injection for Ruby. Find yourself passing dependencies
|
14
28
|
in to the initalizer? Medicine makes this declarative.
|
15
29
|
email:
|
@@ -21,6 +35,8 @@ files:
|
|
21
35
|
- ".gitignore"
|
22
36
|
- ".rspec"
|
23
37
|
- ".ruby-version"
|
38
|
+
- ".travis.yml"
|
39
|
+
- CHANGELOG.md
|
24
40
|
- Gemfile
|
25
41
|
- LICENSE.txt
|
26
42
|
- README.md
|
@@ -58,3 +74,4 @@ summary: Simple Dependency Injection for Ruby
|
|
58
74
|
test_files:
|
59
75
|
- spec/medicine_spec.rb
|
60
76
|
- spec/spec_helper.rb
|
77
|
+
has_rdoc:
|