service_provider 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .redcar
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode
5
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in service_provider.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'rspec'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Johannes Tuchscherer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ ![Build Status](https://secure.travis-ci.org/jtuchscherer/service_provider.png)
2
+
3
+ # ServiceProvider
4
+
5
+ Simple attempt to inject dependencies through method decorators (https://github.com/michaelfairley/method_decorators)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'service_provider'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install service_provider
20
+
21
+ ## Usage
22
+ Service provider comes with an automatic service provider, which registers every provided service under its name. You can specify your own service provider (see below).
23
+
24
+ ### Providing a service
25
+ The class that provides a dependency has to extend the `ServiceProvider` module and to decorate its initialize method with the `Provides` decorator:
26
+
27
+ ```ruby
28
+ class SquareService
29
+ extend ServiceProvider
30
+
31
+ +Provides
32
+ def initialize
33
+ end
34
+ end
35
+ ```
36
+ The (suggested) name of the service provided defaults to the underscored class name, and can also be given as a parameter: `+Provides.new(:square_service)`. The standard service provider used will use this name as the name of the service. Custom service providers (see below) might not make use of this information.
37
+
38
+ ### Using a service
39
+ The class that requires a service has to extend the `MethodDecorators` module and to decorate its initialize method with the `Requires` decorator. The argument passed into the `Requires` decorator will be the name of the instance variable that holds that service.
40
+
41
+ ```ruby
42
+ class SquareSample
43
+ extend MethodDecorators
44
+
45
+ +Requires.new(:square_service)
46
+ def initialize
47
+ end
48
+
49
+ def do_work(num)
50
+ @square_service.square(num)
51
+ end
52
+ end
53
+ ```
54
+
55
+ After a service has been required it can be manually set on the object, through a setter for the instance variable, i.e. `square_service=`.
56
+
57
+ ### Using a custom service provider
58
+ You might want to specify how services are provided when you have multiple classes that implement the same service or when you want to change the implementations completely, i.e. for tests. To do so, provide your own service provider and register it with `ServiceProvider`:
59
+
60
+ ```ruby
61
+ class CustomServiceProvider
62
+ def provide(service_class, service_class_provided_service_name)
63
+ #how to store services
64
+ end
65
+
66
+ def get_service(service_name)
67
+ #how to retrieve services by name
68
+ end
69
+ end
70
+
71
+ ServiceProvider.provider_implementation = CustomServiceProvider.new
72
+ ```
73
+
74
+ ### Known limitatations and uglinesses
75
+ - Each class has to have an empty constructor to put the MethodDecorators before it.
76
+ - Provider has to extend the ServiceProvider, Requirer has to extend MethodDecorators
77
+
78
+ ## Contributing
79
+
80
+ 1. Fork it
81
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
82
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
83
+ 4. Push to the branch (`git push origin my-new-feature`)
84
+ 5. Create new Pull Request
85
+
86
+ ## License
87
+
88
+ Copyright (c) Johannes Tuchscherer
89
+
90
+ Released under the MIT license. See LICENSE file for details.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:rspec) do |spec|
6
+ spec.pattern = 'spec/**/*_spec.rb'
7
+ end
8
+
9
+ task :default => :rspec
@@ -0,0 +1,11 @@
1
+ module ServiceProvider
2
+ module MethodDecorators
3
+ class Provides < MethodDecorator
4
+ attr_reader :service_name
5
+
6
+ def initialize(service_name = nil)
7
+ @service_name = service_name
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ module ServiceProvider
2
+ module MethodDecorators
3
+ class Requires < MethodDecorator
4
+ def initialize(service)
5
+ @service = service
6
+ end
7
+
8
+ def call(orig, this, *args, &blk)
9
+ orig.call(*args, &blk)
10
+ this.instance_variable_set("@#{@service.to_sym}", ServiceProvider.provider.get_service(@service.to_sym))
11
+ add_service_setter(this)
12
+ this
13
+ end
14
+
15
+ def add_service_setter(this)
16
+ service = @service
17
+ this.class.send(:define_method, "#{service}=") do |value|
18
+ instance_variable_set("@#{service}", value)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ module ServiceProvider
2
+ module Provider
3
+ class Automatic
4
+ def self.add_service(requested_service_name, service_class)
5
+ service_name = requested_service_name ? requested_service_name.to_sym : underscore_string(service_class.name).to_sym
6
+ Services.instance.put service_name, service_class
7
+ end
8
+
9
+ def self.get_service(service_name)
10
+ Services.instance.get service_name
11
+ end
12
+
13
+ private
14
+ def self.underscore_string(class_name)
15
+ word = class_name.dup
16
+ word.gsub!(/::/, '/')
17
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
18
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
19
+ word.tr!("-", "_")
20
+ word.downcase!
21
+ word
22
+ end
23
+
24
+ class Services
25
+ include Singleton
26
+
27
+ def initialize
28
+ @services = {}
29
+ @service_constructors = {}
30
+ end
31
+
32
+ def put(service_name, service_class)
33
+ @service_constructors[service_name] = service_class
34
+ end
35
+
36
+ def get(service_name)
37
+ @services[service_name] ||= @service_constructors[service_name].new
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module ServiceProvider
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,58 @@
1
+ require "service_provider/version"
2
+
3
+ require "singleton"
4
+ require "method_decorators"
5
+
6
+ require "service_provider/method_decorators/requires"
7
+ require "service_provider/method_decorators/provides"
8
+ require "service_provider/provider/automatic"
9
+
10
+ module ServiceProvider
11
+ def method_added(method_name)
12
+ super
13
+
14
+ decorators = MethodDecorator.current_decorators
15
+ return if decorators.empty?
16
+
17
+ provider_decorator = find_and_remove_provider_decorator!(decorators)
18
+ call_original_method_with_other_decorators(method_name, decorators)
19
+ add_provider_service_to_service_provider(provider_decorator.service_name) if provider_decorator
20
+ end
21
+
22
+ def self.provider
23
+ @provider ||= ServiceProvider::Provider::Automatic
24
+ end
25
+
26
+ def self.provider=(provider)
27
+ @provider = provider
28
+ end
29
+
30
+ private
31
+
32
+ def add_provider_service_to_service_provider(requested_service_name)
33
+ ServiceProvider.provider.add_service(requested_service_name, self)
34
+ end
35
+
36
+ def call_original_method_with_other_decorators(name, decorators)
37
+ original_method = instance_method(name)
38
+
39
+ define_method(name) do |*args, &blk|
40
+ decorated = ServiceProvider.decorate_callable(original_method.bind(self), decorators)
41
+ decorated.call(*args, &blk)
42
+ end
43
+ end
44
+
45
+ def find_and_remove_provider_decorator!(decorators)
46
+ provide_decorator_index = decorators.index { |decorator| decorator.is_a? Provides }
47
+ provide_decorator = provide_decorator_index ? decorators.delete_at(provide_decorator_index) : nil
48
+ end
49
+
50
+ def self.decorate_callable(orig, decorators)
51
+ decorators.reduce(orig) do |callable, decorator|
52
+ lambda { |*a, &b| decorator.call(callable, orig.receiver, *a, &b) }
53
+ end
54
+ end
55
+ end
56
+
57
+ Kernel.const_set(:Requires, ServiceProvider::MethodDecorators::Requires)
58
+ Kernel.const_set(:Provides, ServiceProvider::MethodDecorators::Provides)
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/service_provider/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Your Name"]
6
+ gem.email = ["jtuchscherer@gmail.com"]
7
+ gem.description = "DI for Ruby"
8
+ gem.summary = "DI for Ruby"
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "service_provider"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ServiceProvider::VERSION
17
+
18
+ gem.add_dependency "method_decorators"
19
+ gem.add_development_dependency "rspec"
20
+ end
@@ -0,0 +1,184 @@
1
+ require "spec_helper"
2
+
3
+ class SquareService
4
+ extend ServiceProvider
5
+
6
+ +Provides.new(:square_service)
7
+ def initialize
8
+ end
9
+
10
+ def square(num)
11
+ num * num
12
+ end
13
+ end
14
+
15
+ class PlusThreeService
16
+ extend ServiceProvider
17
+
18
+ +Provides.new(:plus_three_service)
19
+ def initialize
20
+ end
21
+
22
+ def plus3(num)
23
+ num + 3
24
+ end
25
+ end
26
+
27
+ class MinusTwoService
28
+ extend ServiceProvider
29
+
30
+ +Provides
31
+ def initialize
32
+ end
33
+
34
+ def minus2(num)
35
+ num - 2
36
+ end
37
+ end
38
+
39
+ class SuperMathService
40
+ extend ServiceProvider
41
+
42
+ +Requires.new(:square_service)
43
+ +Requires.new(:plus_three_service)
44
+ +Provides.new("super_math_service")
45
+ def initialize
46
+ end
47
+
48
+ def calculate(num)
49
+ @plus_three_service.plus3 @square_service.square(num)
50
+ end
51
+ end
52
+
53
+ describe ServiceProvider do
54
+ describe "providing a simple service" do
55
+ it "should work for one client using one service" do
56
+ class SquareSample
57
+ extend MethodDecorators
58
+
59
+ +Requires.new(:square_service)
60
+ def initialize
61
+ end
62
+
63
+ def do_work(num)
64
+ @square_service.square (num)
65
+ end
66
+ end
67
+
68
+ SquareSample.new.do_work(2).should == 4
69
+ end
70
+
71
+ it "should work when specifying the service as a string" do
72
+ class SquareSample
73
+ extend MethodDecorators
74
+
75
+ +Requires.new("square_service")
76
+ def initialize
77
+ end
78
+
79
+ def do_work(num)
80
+ @square_service.square (num)
81
+ end
82
+ end
83
+
84
+ SquareSample.new.do_work(2).should == 4
85
+ end
86
+
87
+ it "should work when not specifying the service name" do
88
+ class SquareSample
89
+ extend MethodDecorators
90
+
91
+ +Requires.new("minus_two_service")
92
+ def initialize
93
+ end
94
+
95
+ def do_work(num)
96
+ @minus_two_service.minus2 (num)
97
+ end
98
+ end
99
+
100
+ SquareSample.new.do_work(4).should == 2
101
+ end
102
+
103
+ it "should work for one client using multiple service" do
104
+ class SquareSample
105
+ extend MethodDecorators
106
+
107
+ +Requires.new(:square_service)
108
+ +Requires.new(:plus_three_service)
109
+ def initialize
110
+ end
111
+
112
+ def do_work(num)
113
+ @plus_three_service.plus3 @square_service.square(num)
114
+ end
115
+ end
116
+
117
+ SquareSample.new.do_work(2).should == 7
118
+ end
119
+
120
+ it "should work for a client that requires a service which requires a service" do
121
+ class SuperMathSample
122
+ extend MethodDecorators
123
+
124
+ +Requires.new(:super_math_service)
125
+ def initialize
126
+ end
127
+
128
+ def do_work(num)
129
+ @super_math_service.calculate(num)
130
+ end
131
+ end
132
+
133
+ SuperMathSample.new.do_work(19).should == 364
134
+ end
135
+
136
+ it "should be possible to override the service by setting it explicitly" do
137
+ class SquareOverrideSample
138
+ extend MethodDecorators
139
+
140
+ +Requires.new(:square_service)
141
+ def initialize
142
+ end
143
+
144
+ def do_work(num)
145
+ @square_service.square (num)
146
+ end
147
+ end
148
+
149
+ square_service_mock = mock('SquareService', square: "square of a number")
150
+
151
+ square_override_sample = SquareOverrideSample.new
152
+ square_override_sample.square_service = square_service_mock
153
+ square_override_sample.do_work(2).should == "square of a number"
154
+ end
155
+
156
+ it "should be possible to override the service provider used" do
157
+ class SquareSample
158
+ extend MethodDecorators
159
+
160
+ +Requires.new(:square_service)
161
+ def initialize
162
+ end
163
+
164
+ def do_work(num)
165
+ @square_service.square (num)
166
+ end
167
+ end
168
+
169
+ square_service_mock = mock('SquareService', square: Math::PI)
170
+
171
+ service_provider_mock = mock('ServiceProvider')
172
+ service_provider_mock.stub(:add_service)
173
+ service_provider_mock.stub(:get_service).with(:square_service).and_return(square_service_mock)
174
+
175
+ ServiceProvider.provider = service_provider_mock
176
+
177
+ SquareSample.new.do_work(999).should == Math::PI
178
+
179
+ #cleanup
180
+ ServiceProvider.provider = nil
181
+ end
182
+ end
183
+ end
184
+
@@ -0,0 +1,20 @@
1
+ require "rspec"
2
+ require "service_provider"
3
+
4
+ # This file was generated by the `rspec --init` command. Conventionally, all
5
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6
+ # Require this file using `require "spec_helper"` to ensure that it is only
7
+ # loaded once.
8
+ #
9
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: service_provider
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Your Name
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-24 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: method_decorators
16
+ requirement: &70264872728480 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70264872728480
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &70264872728060 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70264872728060
36
+ description: DI for Ruby
37
+ email:
38
+ - jtuchscherer@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - .gitignore
44
+ - .rspec
45
+ - .travis.yml
46
+ - Gemfile
47
+ - LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - lib/service_provider.rb
51
+ - lib/service_provider/method_decorators/provides.rb
52
+ - lib/service_provider/method_decorators/requires.rb
53
+ - lib/service_provider/provider/automatic.rb
54
+ - lib/service_provider/version.rb
55
+ - service_provider.gemspec
56
+ - spec/service_provider_spec.rb
57
+ - spec/spec_helper.rb
58
+ homepage: ''
59
+ licenses: []
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 1.8.6
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: DI for Ruby
82
+ test_files:
83
+ - spec/service_provider_spec.rb
84
+ - spec/spec_helper.rb