service_bureau 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in service_bureau.gemspec
4
+ gemspec
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,3 @@
1
+ module ServiceBureau
2
+ VERSION = "0.0.1"
3
+ 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: