service_bureau 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +128 -0
- data/Rakefile +1 -0
- data/lib/service_bureau/locations.rb +26 -0
- data/lib/service_bureau/locator.rb +25 -0
- data/lib/service_bureau/services.rb +30 -0
- data/lib/service_bureau/version.rb +3 -0
- data/lib/service_bureau.rb +9 -0
- data/service_bureau.gemspec +24 -0
- data/spec/service_bureau_spec.rb +110 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjdhMDFkZTZlNTMxMWMyMzA1YTRlMDQ5ODU1Yzg1ZjQyMDE3YTFlZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OGE4OTI2NDI3MTE2MWZiODM0MGE1YTZjZWU1M2RjYzY0MzcyMzM5ZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWUzY2IyMDQ5MjVjOTNlNDYxYWM2Nzk4NjkwOWIwNTMyYWFjYjE5NDJiZjVi
|
10
|
+
ZjkyNjBhYjhlMzU4Y2U5MDVmNWRiYmYyMWZjNDRmZTdlNTNjNWRiZDkzZjU4
|
11
|
+
MGY1ZTc5NzQ1MzA4Njg5MjRlMDYxMWNlZWZkOWRmYzZjYzEzMTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NWM0YzgwZDc5NmEwMDU4ODIyMzg1ZGE4YmQ2NjczOThmZDg2M2Y2ZDhjNmE1
|
14
|
+
ZDRiOWQ1Y2Y5MDNmNzdlZDIzM2UwMzg1NWI1MDJhODA1NDQ5OGMxMGIyYmVm
|
15
|
+
MWJkNmM2ZGI5NDhjNjJjODVmODhkNzhkYjY3NzM5YzA3YjI2ZmE=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Matt Van Horn
|
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,128 @@
|
|
1
|
+
# ServiceBureau
|
2
|
+
|
3
|
+
ServiceBureau provides an easy way to use extracted service objects, as described
|
4
|
+
by Bryan Helmkamp, in this great article on dealing with fat models: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'service_bureau'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install service_bureau
|
19
|
+
|
20
|
+
## Configuration
|
21
|
+
|
22
|
+
The ServiceBureau is configured with names of services and factories that
|
23
|
+
will provide an instance of the service. For services that are classes instead of
|
24
|
+
instances, you can provide a lambda that returns the class.
|
25
|
+
|
26
|
+
A factory can be any object that responds to `#call` - a Proc, lambda, method obj, etc.
|
27
|
+
|
28
|
+
If your service requires arguments to initialize it, make sure the proc accepts them.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
ServiceBureau::Locations.configure do |config|
|
32
|
+
config.my_service ->{ MyService.instance }
|
33
|
+
config.another_service AnotherService.public_method(:new)
|
34
|
+
config.a_svc_that_needs_args ->(arg1, arg2){ OtherService.new(arg1).setup(arg2) }
|
35
|
+
config.static_service ->{ ServiceClass }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
In your client classes, you can use this a couple of ways:
|
42
|
+
|
43
|
+
### As a mixin:
|
44
|
+
|
45
|
+
You get a handle to the service:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class MyClass
|
49
|
+
include ServiceBureau::Services
|
50
|
+
|
51
|
+
def my_method
|
52
|
+
result = my_service.do_something
|
53
|
+
end
|
54
|
+
|
55
|
+
# if initialization is needed, just pass in the args.
|
56
|
+
def my_method
|
57
|
+
result = my_service(42).do_something
|
58
|
+
end
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
And you get an injector to keep your tests fast and decoupled:
|
63
|
+
```ruby
|
64
|
+
describe MyClass
|
65
|
+
let(:stubbed_service){ double('fake service', do_something: "a result") }
|
66
|
+
|
67
|
+
subject(:my_obj){ MyClass.new }
|
68
|
+
|
69
|
+
before do
|
70
|
+
my_class.my_service = stubbed_service
|
71
|
+
end
|
72
|
+
|
73
|
+
# stubbed
|
74
|
+
it "does something" do
|
75
|
+
expect(my_class.my_method).to eq("a result")
|
76
|
+
end
|
77
|
+
|
78
|
+
# mocked
|
79
|
+
it "does something" do
|
80
|
+
expect(stubbed_service).to receive(:do_something).and_return("a result")
|
81
|
+
my_class.my_method
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### As a regular class method lookup
|
87
|
+
```ruby
|
88
|
+
class MyClass
|
89
|
+
def my_method
|
90
|
+
service = ServiceBureau::Locator.get_service(:my_service, 'any', 'arguments')
|
91
|
+
result = service.do_something
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
The handle to the instance will memoize it, and only instantiate it the first time it is called.
|
97
|
+
If you need a new instance, you can use the injector to clear out the old one,
|
98
|
+
or you can use the class method on Locator to get a new instance (and you can use the injector
|
99
|
+
if you want to replace the old one.)
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
class MyClass
|
103
|
+
include ServiceBureau::Services
|
104
|
+
|
105
|
+
def use_service_with_foo
|
106
|
+
result = my_service(:foo).do_something
|
107
|
+
end
|
108
|
+
|
109
|
+
def use_service_with_bar
|
110
|
+
service = ServiceBureau::Locator.get_service(:my_service, :bar)
|
111
|
+
result = service.do_something
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_service_to_baz
|
115
|
+
new_service_obj = ServiceBureau::Locator.get_service(:my_service, :baz)
|
116
|
+
my_service = new_service_obj
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
1. Fork it
|
125
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
126
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
127
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
128
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ServiceBureau
|
2
|
+
class Locations
|
3
|
+
class << self
|
4
|
+
def configure(&block)
|
5
|
+
yield self
|
6
|
+
end
|
7
|
+
|
8
|
+
def factory_map
|
9
|
+
@factory_map ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def clear
|
13
|
+
factory_map.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method_name, *args, &block)
|
17
|
+
if args.size == 1 && !block_given?
|
18
|
+
factory_map[method_name] = args.first
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ServiceBureau
|
2
|
+
class Locator
|
3
|
+
def initialize
|
4
|
+
@service_factories = ServiceBureau::Locations.factory_map
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_service service_key, *args
|
8
|
+
service_factories.fetch(service_key).call *args
|
9
|
+
|
10
|
+
rescue KeyError
|
11
|
+
raise UnknownServiceError.new("Cannot locate factory for '#{service_key}' - please check your configuration")
|
12
|
+
|
13
|
+
rescue NoMethodError => err
|
14
|
+
if err.message =~ /undefined method `call'/
|
15
|
+
raise UncallableFactoryError.new("The factory registered for '#{service_key}' did not respond to #call")
|
16
|
+
else
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :service_factories
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ServiceBureau
|
2
|
+
module Services
|
3
|
+
def self.included(base)
|
4
|
+
ServiceBureau::Locations.factory_map.keys.each do |service|
|
5
|
+
# Provide a method to inject the service obj
|
6
|
+
define_method "#{service}=" do |service_obj|
|
7
|
+
__set_service__ service, service_obj
|
8
|
+
end
|
9
|
+
|
10
|
+
# Provide a handle to the service instance
|
11
|
+
define_method service do |*args|
|
12
|
+
__get_service__ service, *args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def __set_service__(key, service_obj)
|
20
|
+
instance_variable_set :"@__#{key}__", service_obj
|
21
|
+
end
|
22
|
+
|
23
|
+
def __get_service__(key, *args)
|
24
|
+
memoized = instance_variable_get :"@__#{key}__"
|
25
|
+
return memoized || begin
|
26
|
+
__set_service__ key, ServiceBureau::Locator.new.get_service(key, *args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "service_bureau/version"
|
2
|
+
require "service_bureau/locations"
|
3
|
+
require "service_bureau/locator"
|
4
|
+
require "service_bureau/services"
|
5
|
+
|
6
|
+
module ServiceBureau
|
7
|
+
class UnknownServiceError < StandardError;end
|
8
|
+
class UncallableFactoryError < StandardError;end
|
9
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'service_bureau/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "service_bureau"
|
8
|
+
spec.version = ServiceBureau::VERSION
|
9
|
+
spec.authors = ["Matt Van Horn"]
|
10
|
+
spec.email = ["mattvanhorn@gmail.com"]
|
11
|
+
spec.description = %q{Something of a cross between a Service Locator and an Abstract Factory to enable easier use of the ServiceObject pattern.}
|
12
|
+
spec.summary = %q{An easy way to provide access to your service classes without creating a lot of coupling}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
24
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "service_bureau"
|
2
|
+
|
3
|
+
describe ServiceBureau::Locator do
|
4
|
+
let(:a_service_obj){ double('some service instance') }
|
5
|
+
let(:factory){ ->(arg){ double('other service') } }
|
6
|
+
|
7
|
+
subject(:locator){ ServiceBureau::Locator.new }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
ServiceBureau::Locations.configure do |config|
|
11
|
+
config.bones_mccoy "Dammit, Jim! I'm a String, not a callable."
|
12
|
+
config.some_service ->{ a_service_obj }
|
13
|
+
config.other_service factory
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#get_service' do
|
18
|
+
it "gets a configured service" do
|
19
|
+
found_svc = locator.get_service :some_service
|
20
|
+
expect(found_svc).to equal a_service_obj
|
21
|
+
end
|
22
|
+
|
23
|
+
it "instantiates a service with args given" do
|
24
|
+
expect(factory).to receive(:call).with('foo')
|
25
|
+
locator.get_service :other_service, 'foo'
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when no service is registered' do
|
29
|
+
it "raises UnknownServiceError" do
|
30
|
+
expect{ locator.get_service :no_such_svc }
|
31
|
+
.to raise_error(ServiceBureau::UnknownServiceError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when the factory registered is not callable' do
|
36
|
+
it "raises UncallableFactoryError" do
|
37
|
+
expect{ locator.get_service :bones_mccoy }
|
38
|
+
.to raise_error(ServiceBureau::UncallableFactoryError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe ServiceBureau::Locations do
|
45
|
+
describe ".configure" do
|
46
|
+
it "should not register a service with no arguments" do
|
47
|
+
expect{ ServiceBureau::Locations.configure { |cfg| cfg.no_args } }.to raise_error(NoMethodError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not register a service with multiple arguments" do
|
51
|
+
expect{ ServiceBureau::Locations.configure { |cfg| cfg.two_args :foo, :bar } }.to raise_error(NoMethodError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not register a service with a block argument" do
|
55
|
+
expect{ ServiceBureau::Locations.configure { |cfg| cfg.block_args(:foo){ :bar } } }.to raise_error(NoMethodError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.clear' do
|
60
|
+
before(:each) do
|
61
|
+
ServiceBureau::Locations.configure do |config|
|
62
|
+
config.a_service :foo
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should clear the factory_map" do
|
67
|
+
ServiceBureau::Locations.clear
|
68
|
+
expect(ServiceBureau::Locations.factory_map).to eq({})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '.factory_map' do
|
73
|
+
before(:each) do
|
74
|
+
ServiceBureau::Locations.clear
|
75
|
+
ServiceBureau::Locations.configure do |config|
|
76
|
+
config.a_service :foo
|
77
|
+
config.another_service :bar
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return the hash of factories" do
|
82
|
+
expect(ServiceBureau::Locations.factory_map).to eq(a_service: :foo, another_service: :bar)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe ServiceBureau::Services do
|
88
|
+
let(:a_service){ double('some service') }
|
89
|
+
let(:factory){ ->(arg){ a_service } }
|
90
|
+
let(:locator){ ServiceBureau::Locator.new }
|
91
|
+
|
92
|
+
subject(:test_obj){ (Class.new{include ServiceBureau::Services}).new }
|
93
|
+
|
94
|
+
before(:each) do
|
95
|
+
ServiceBureau::Locations.clear
|
96
|
+
ServiceBureau::Locations.configure do |config|
|
97
|
+
config.my_service factory
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "provides access to a service instance" do
|
102
|
+
expect(test_obj.my_service(42)).to equal(a_service)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "provides a way to inject a different service object (e.g. for tests)" do
|
106
|
+
injected = double('injected')
|
107
|
+
test_obj.my_service = injected
|
108
|
+
expect(test_obj.my_service(42)).to equal(injected)
|
109
|
+
end
|
110
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: service_bureau
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Van Horn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.14'
|
55
|
+
description: Something of a cross between a Service Locator and an Abstract Factory
|
56
|
+
to enable easier use of the ServiceObject pattern.
|
57
|
+
email:
|
58
|
+
- mattvanhorn@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/service_bureau.rb
|
69
|
+
- lib/service_bureau/locations.rb
|
70
|
+
- lib/service_bureau/locator.rb
|
71
|
+
- lib/service_bureau/services.rb
|
72
|
+
- lib/service_bureau/version.rb
|
73
|
+
- service_bureau.gemspec
|
74
|
+
- spec/service_bureau_spec.rb
|
75
|
+
homepage: ''
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.1.4
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: An easy way to provide access to your service classes without creating a
|
99
|
+
lot of coupling
|
100
|
+
test_files:
|
101
|
+
- spec/service_bureau_spec.rb
|
102
|
+
has_rdoc:
|