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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 19f0f8412a402f42bd7b0f91644a187d664e6e29
4
- data.tar.gz: ec738b16e8c28da75bd08f4fa660aab74b33b6f7
3
+ metadata.gz: 79bc0ab2a504f8fac7530336253908d4d7a79332
4
+ data.tar.gz: dbd4d812702841a2182eee8506bcd650144190a0
5
5
  SHA512:
6
- metadata.gz: 73d44fa17647302412a37d9055e8770c0505e5d7468fb30bd8499c6cca3b5df10722a4aaa1e9ce61cd858efdfc8903332413f20967980dde8b261211932e1856
7
- data.tar.gz: d40fecb0a6d6d0af603fc698f959887fd5753a9fae779b2f533d5f5c5cc23e463952625b70943a5280232daf1efa7bfe56916fc9f76d8cbd78c1623fe0cfe04d
6
+ metadata.gz: 8677b2d572b120218ee4fb4a9812948673d2eff51fc703a973f88029a82b77076fbf6d80fe3e90e00af5964c28c945a8cbdf869422e71703b9d18fd29a1c82b5
7
+ data.tar.gz: 24a18976d5119c76c0a56f6e115382c87c1786060613bedadaf63ff9a8a3b331b3f54289212e6b593771b3436cbcf3ea559aeaa718196f6f19c87f15377ffa0a
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ bundler_args: --without=extras
3
+ script: rspec spec
4
+ rvm:
5
+ - '2.1.5'
6
+ - rbx-2
7
+ matrix:
8
+ include:
9
+ - rvm: jruby
10
+ env: JRUBY_OPTS="--2.1"
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
@@ -3,6 +3,11 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem "bundler"
6
- gem "rake"
7
6
  gem "rspec"
8
- gem 'pry-byebug'
7
+ gem 'coveralls', require: false
8
+
9
+ group :extras do
10
+ gem "rake"
11
+ gem 'pry-byebug'
12
+ gem 'yard'
13
+ end
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
- # or
38
+ If you want to arguments other than the dependencies in to the constructor
39
+ don't forget to invoke `super`:
33
40
 
34
- cast_vote = CastVote.new(arg1, arg2, vote_repo: vote_repo)
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 error will be raised on
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.1
9
+ release 0.0.2
@@ -1,3 +1,3 @@
1
1
  module Medicine
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
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
- module DI
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(*args)
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 ArgumentError, "initalize with all dependencies without a default" unless all_dependencies_met?
44
+ raise RequiredDependencyError, "pass all required dependencies (#{unmet_dependencies.join(', ')}) in to initialize" unless unmet_dependencies.empty?
39
45
  end
40
46
 
41
- def all_dependencies_met?
42
- self.class.dependencies.keys.all? do |key|
43
- @dependencies.has_key?(key) || self.class.dependencies.fetch(key).has_key?(:default)
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 dependencies
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
@@ -17,4 +17,6 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'inflecto'
20
22
  end
@@ -1,90 +1,165 @@
1
1
  RSpec.describe 'Medicine' do
2
- let(:klass) do
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 '.dependency' do
15
+ describe 'dependency declarations' do
16
+ it 'survive inheritence' do
17
+ medicated_class.class_eval { dependency :foobar }
9
18
 
10
- describe 'without any options' do
11
- before { klass.class_eval { dependency :vote_repo } }
19
+ super_medicated_class = Class.new(medicated_class)
20
+ expect(super_medicated_class.dependencies).not_to be_empty
21
+ end
12
22
 
13
- context 'initialized with no arguments' do
14
- subject { klass.new }
23
+ it 'do not leak between unrelated classes' do
24
+ medicated_class.class_eval { dependency :foobar }
15
25
 
16
- it 'initialization raises error' do
17
- expect { subject }.to raise_error(Medicine::DI::ArgumentError)
18
- end
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 'when initalized with hash' do
22
- subject { klass.new(vote_repo: :foo) }
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
- context 'and hash has key for dependency' do
25
- it 'responds to dependency name' do # TODO: method should be private
26
- expect(subject).to respond_to(:vote_repo)
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
- it 'provides method which returns injected value' do
30
- expect(subject.vote_repo).to eq :foo
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
- context 'hash has no key for dependency' do
35
- subject { klass.new({}) }
59
+ context 'and hash has no key for dependency' do
60
+ subject { medicated_class.new({}) }
36
61
 
37
- it 'raises error' do
38
- expect { subject }.to raise_error(Medicine::DI::ArgumentError)
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
- context 'initalized with arguments and hash' do
44
- before do
45
- klass.class_eval do
46
- def initialize(arg1, arg2, deps)
47
- super
48
- end
49
- end
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
- context 'and hash has key for dependency' do
53
- subject { klass.new(double, double, vote_repo: :foo) }
88
+ describe 'as a lambda' do
89
+ before { medicated_class.class_eval { dependency :vote_repo, default: -> { Foo } } }
54
90
 
55
- skip 'provides method for dependency' do
56
- expect(subject).to respond_to(:vote_repo)
57
- end
91
+ it 'returns a class' do
92
+ expect(subject._vote_repo).to eq Foo
58
93
  end
94
+ end
59
95
 
60
- context 'hash has no key for dependency' do
61
- subject { klass.new(double, double, {}) }
96
+ describe 'as a Proc' do
97
+ before { medicated_class.class_eval { dependency :vote_repo, default: Proc.new { Foo } } }
62
98
 
63
- skip 'raises error' do
64
- expect { subject }.to raise_error(Medicine::DI::ArgumentError)
65
- end
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
- describe 'with default option' do
71
- describe 'of type String' do
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
- context 'and initializer given no arguments' do
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
- subject { klass.new }
123
+ it 'returns a class' do
124
+ expect(subject._vote_repo).to eq Foo
125
+ end
126
+ end
77
127
 
78
- it 'responds to dependency name' do
79
- expect(subject).to respond_to(:vote_repo)
80
- end
128
+ describe 'as a lowercase Symbol' do
129
+ before { medicated_class.class_eval { dependency :vote_repo, default: :foo} }
81
130
 
82
- it 'returns given default typecast to a class' do
83
- expect(subject.vote_repo).to be_an_instance_of(Object)
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
- end # .dependency
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
@@ -1,4 +1,11 @@
1
- require 'pry'
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ begin
5
+ require 'pry'
6
+ rescue LoadError
7
+ end
8
+
2
9
  require 'medicine'
3
10
 
4
11
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
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.1
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-09 00:00:00.000000000 Z
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: