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