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 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: