service_bureau 0.0.1
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 +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:
|