medicine 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 79bc0ab2a504f8fac7530336253908d4d7a79332
4
- data.tar.gz: dbd4d812702841a2182eee8506bcd650144190a0
3
+ metadata.gz: 9bd32dce92b989b0d76ecc791f5a2306df67c890
4
+ data.tar.gz: 0c75aa02a7c6835a7138323800930a8556d885a6
5
5
  SHA512:
6
- metadata.gz: 8677b2d572b120218ee4fb4a9812948673d2eff51fc703a973f88029a82b77076fbf6d80fe3e90e00af5964c28c945a8cbdf869422e71703b9d18fd29a1c82b5
7
- data.tar.gz: 24a18976d5119c76c0a56f6e115382c87c1786060613bedadaf63ff9a8a3b331b3f54289212e6b593771b3436cbcf3ea559aeaa718196f6f19c87f15377ffa0a
6
+ metadata.gz: a08a91ebe6927ea99a50659ac099f3be1ff27ab0191438c8b9d489e202438b2b4a96aeac6959b074866cfbe8b5b276f35b367a51cb80da2bdfd2b51eeaac1b49
7
+ data.tar.gz: 5d55b2ee2e909e30f91210bb3b43980b44d2fbc9b3eb99313a39f60bf4151f53d98b4acc4c2e7f9708b0707333f3812ab9ae6e93d5897ca08606e49eac4c2653
@@ -1 +1 @@
1
- 2.1.5
1
+ 2.3
@@ -2,9 +2,5 @@ language: ruby
2
2
  bundler_args: --without=extras
3
3
  script: rspec spec
4
4
  rvm:
5
- - '2.1.5'
5
+ - '2.3'
6
6
  - rbx-2
7
- matrix:
8
- include:
9
- - rvm: jruby
10
- env: JRUBY_OPTS="--2.1"
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.0.0 (01/10/2016)
6
+
7
+ * rename `inject` to `inject_dependency` to avoid clash with collection-like
8
+ objects which might have an `inject` method already. [breaking change]
9
+
5
10
  ## 0.0.2 (13th Feb 2015)
6
11
 
7
12
  * refactoring
data/Gemfile CHANGED
@@ -8,6 +8,9 @@ gem 'coveralls', require: false
8
8
 
9
9
  group :extras do
10
10
  gem "rake"
11
+ gem 'guard-rspec'
11
12
  gem 'pry-byebug'
12
13
  gem 'yard'
14
+ gem 'redcarpet' # yard
15
+ gem 'inch'
13
16
  end
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec', all_on_start: true, all_after_pass: true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/README.md CHANGED
@@ -4,11 +4,17 @@
4
4
  [![Code Climate](https://codeclimate.com/github/krisleech/medicine.png)](https://codeclimate.com/github/krisleech/medicine)
5
5
  [![Build Status](https://travis-ci.org/krisleech/medicine.png?branch=master)](https://travis-ci.org/krisleech/medicine)
6
6
  [![Coverage Status](https://coveralls.io/repos/krisleech/medicine/badge.png?branch=master)](https://coveralls.io/r/krisleech/medicine?branch=master)
7
+ [![Inch Pages](http://inch-ci.org/github/krisleech/medicine.png)](http://inch-ci.org/github/krisleech/medicine)
7
8
 
8
9
  Simple Dependency Injection for Ruby
9
10
 
10
- Find yourself passing dependencies in to the initalizer? Medicine makes this
11
- declarative.
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(entry_id)
20
- vote_repo.create!(entry_id: entry_id)
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
- cast_vote = CastVote.new
26
- cast_vote.call(3)
36
+ ```ruby
37
+ command = CastVote.new
27
38
  ```
28
39
 
29
- In this example Medicine adds a private method called `vote_repo` which returns `Vote`.
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
- vote_repo = double('VoteRepo')
35
- cast_vote = CastVote.new(vote_repo: vote_repo)
52
+ command = CastVote.new(votes_repo: double)
36
53
  ```
37
54
 
38
- If you want to arguments other than the dependencies in to the constructor
39
- don't forget to invoke `super`:
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
- def initialize(arg1, arg2, dependencies = {})
43
- @arg1 = arg1
44
- @arg2 = arg2
45
- super(dependencies)
46
- end
63
+ command = CastVote.new
64
+ command.inject_depdendency(:vote_repo, double)
47
65
  ```
48
66
 
49
- ## Required dependencies
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 and is not injected an exception will be raised on
56
- initialization.
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
- ## Default dependencies
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
- The above examples will expose a method called `vote_repo` which returns the
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
@@ -1,81 +1,126 @@
1
1
  require "medicine/version"
2
- require "inflecto"
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
- def self.included(base)
13
- base.extend(ClassMethods)
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
- def self.prepended(base)
17
- base.extend(ClassMethods)
18
- end
19
-
20
- def initialize(*args)
21
- @dependencies = extract_dependencies(args)
22
- assert_all_dependencies_met
23
- define_dependency_methods
24
-
25
- super
26
- end
27
-
28
- private
29
-
30
- def define_dependency_methods
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
- def extract_dependencies(args)
40
- args.last.respond_to?(:[]) ? args.pop : {}
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
- def assert_all_dependencies_met
44
- raise RequiredDependencyError, "pass all required dependencies (#{unmet_dependencies.join(', ')}) in to initialize" unless unmet_dependencies.empty?
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
- def unmet_dependencies
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 resolve_dependency(name)
54
- typecast_dependency(self.class.dependencies.fetch(name).fetch(:default))
84
+ def self.included(base)
85
+ base.extend(ClassMethods)
55
86
  end
56
87
 
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
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[name] = options
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
@@ -1,3 +1,3 @@
1
1
  module Medicine
2
- VERSION = "0.0.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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
@@ -12,61 +12,35 @@ RSpec.describe 'Medicine' do
12
12
  end
13
13
  end
14
14
 
15
- describe 'dependency declarations' do
16
- it 'survive inheritence' do
17
- medicated_class.class_eval { dependency :foobar }
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 'do not leak between unrelated classes' do
24
- medicated_class.class_eval { dependency :foobar }
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 without any options' do
31
+ describe 'when dependency declared with no default' do
32
32
  before { medicated_class.class_eval { dependency :vote_repo } }
33
33
 
34
- context 'and subject initialized with no arguments' do
34
+ context 'and dependency not injected' do
35
35
  subject { medicated_class.new }
36
36
 
37
- it 'raises exception' do
38
- expect { subject }.to raise_error(Medicine::RequiredDependencyError)
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
- context 'and subject initialized with no arguments' do
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 '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 } }
52
+ describe 'as a class' do
53
+ before { medicated_class.class_eval { dependency :vote_repo, default: Foo } }
82
54
 
83
- it 'returns a class' do
84
- expect(subject._vote_repo).to eq Foo
85
- end
55
+ it 'returns a class' do
56
+ expect(subject._vote_repo).to eq Foo
86
57
  end
58
+ end
87
59
 
88
- describe 'as a lambda' do
89
- before { medicated_class.class_eval { dependency :vote_repo, default: -> { Foo } } }
60
+ describe 'as a lambda' do
61
+ before { medicated_class.class_eval { dependency :vote_repo, default: -> { Foo } } }
90
62
 
91
- it 'returns a class' do
92
- expect(subject._vote_repo).to eq Foo
93
- end
63
+ it 'returns a class' do
64
+ expect(subject._vote_repo).to eq Foo
94
65
  end
66
+ end
95
67
 
96
- describe 'as a Proc' do
97
- before { medicated_class.class_eval { dependency :vote_repo, default: Proc.new { Foo } } }
68
+ describe 'as a Proc' do
69
+ before { medicated_class.class_eval { dependency :vote_repo, default: Proc.new { Foo } } }
98
70
 
99
- it 'returns a class' do
100
- expect(subject._vote_repo).to eq Foo
101
- end
71
+ it 'returns a class' do
72
+ expect(subject._vote_repo).to eq Foo
102
73
  end
74
+ end
103
75
 
104
- describe 'as a CamelCase String' do
105
- before { medicated_class.class_eval { dependency :vote_repo, default: 'Foo' } }
76
+ describe 'as a CamelCase String' do
77
+ before { medicated_class.class_eval { dependency :vote_repo, default: 'Foo' } }
106
78
 
107
- it 'returns a class' do
108
- expect(subject._vote_repo).to eq Foo
109
- end
79
+ it 'returns a class' do
80
+ expect(subject._vote_repo).to eq Foo
110
81
  end
82
+ end
111
83
 
112
- describe 'as a plural CamelCase String' do
113
- before { medicated_class.class_eval { dependency :vote_repo, default: 'Foos' } }
84
+ describe 'as a plural CamelCase String' do
85
+ before { medicated_class.class_eval { dependency :vote_repo, default: 'Foos' } }
114
86
 
115
- it 'returns a class' do
116
- expect(subject._vote_repo).to eq Foos
117
- end
87
+ it 'returns a class' do
88
+ expect(subject._vote_repo).to eq Foos
118
89
  end
90
+ end
119
91
 
120
- describe 'as a CamelCase Symbol' do
121
- before { medicated_class.class_eval { dependency :vote_repo, default: :Foo} }
92
+ describe 'as a CamelCase Symbol' do
93
+ before { medicated_class.class_eval { dependency :vote_repo, default: :Foo} }
122
94
 
123
- it 'returns a class' do
124
- expect(subject._vote_repo).to eq Foo
125
- end
95
+ it 'returns a class' do
96
+ expect(subject._vote_repo).to eq Foo
126
97
  end
98
+ end
127
99
 
128
- describe 'as a lowercase Symbol' do
129
- before { medicated_class.class_eval { dependency :vote_repo, default: :foo} }
100
+ describe 'as a lowercase Symbol' do
101
+ before { medicated_class.class_eval { dependency :vote_repo, default: :foo} }
130
102
 
131
- it 'returns a class' do
132
- expect(subject._vote_repo).to eq Foo
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 # rspec
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.2
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: 2015-02-13 00:00:00.000000000 Z
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.2.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: