medicine 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/medicine.png)](http://badge.fury.io/rb/medicine)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/krisleech/medicine.png)](https://codeclimate.com/github/krisleech/medicine)
|
5
|
+
[![Build Status](https://travis-ci.org/krisleech/medicine.png?branch=master)](https://travis-ci.org/krisleech/medicine)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/krisleech/medicine/badge.png?branch=master)](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:
|