interchange 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a484f8b2614fe9877352ed14caa807a673da3aae
4
+ data.tar.gz: 69469458f495d8f19e28fdda45c9c36610b1c628
5
+ SHA512:
6
+ metadata.gz: 95a63d557d7abdee3e718906c8d691c396e3694f363af90ef27fe675fe2457699b80bf4e56063791d2c91014aeaa8a33cc7e6de3b60b9a9a7e03465128bdfbb7
7
+ data.tar.gz: bb85bf52e17436c6d6ddbb353c752c676e0c07abbd017e7ebd3d7b84082dcda5d28ccc6326d3395a8cc0c0c9c4aa04b813ec9fa089f88a7b04196fb2611f11b9
@@ -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
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - "1.9.3"
3
+ - "2.0.0"
4
+ - "2.1.0"
5
+ - "jruby-19mode"
6
+ - "rbx-19mode"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in interchange.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 ahawkins
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.
@@ -0,0 +1,100 @@
1
+ # Interchange
2
+
3
+ `Interchange` is a way to define boundary objects. The class
4
+ defines the all required methods, then delegates the work to an
5
+ implementation. Implementations are be registered and used. A null
6
+ implementation is automatically generated and set as the
7
+ default implementation. This is useful in a few use cases:
8
+
9
+ * SOA setups where clients must be isolated from services (EX:
10
+ different implementations in dev/test/production)
11
+ * Isolating different sub-systems
12
+
13
+ Here are some examples.
14
+
15
+ ```ruby
16
+ class Mailer
17
+ include Interchange.new(:deliver, :deliveries)
18
+ end
19
+
20
+ class SMTPDelivery
21
+ def deliver(mail)
22
+ # send w/SMTP
23
+ end
24
+
25
+ def deliveries
26
+ # check the email account
27
+ end
28
+ end
29
+
30
+ class SnailMail
31
+ def deliver(mail)
32
+ # print the mail and go to the post office
33
+ end
34
+
35
+ def deliveries
36
+ # go outside and check the mailbox
37
+ end
38
+ end
39
+
40
+ mailer = Mailer.new
41
+ mailer.register :smtp, SMTPDelivery.new
42
+ mailer.register :snail_mail, SnailMail.new
43
+
44
+ mail.use :smtp
45
+ mail.deliver some_message
46
+
47
+ mail.use :null # switch back to the null implementation.
48
+ ```
49
+
50
+ These objects are useful when you have an interaction that needs
51
+ to happen but implementations can vary widely. You can also use this
52
+ as class if you don't like the instance flavor.
53
+
54
+ ```ruby
55
+ class Mailer
56
+ extend Interchange.new(:foo, :bar, :bar)
57
+ end
58
+
59
+ Mailer.register, :smtp, SomeSmtpClass
60
+ ```
61
+
62
+ Since `Interchange.new` returns a new module, you can call define
63
+ methods and call `super` just like normal.
64
+
65
+ ```ruby
66
+ class Mailer
67
+ include Interchange.new(:deliver)
68
+
69
+ def deliver(mail)
70
+ raise "No address" unless mail.to
71
+ super
72
+ end
73
+ end
74
+ ```
75
+
76
+ This is great when you have some shared logic at the boundary but not
77
+ across implementations.
78
+
79
+ ## Installation
80
+
81
+ Add this line to your application's Gemfile:
82
+
83
+ gem 'interchange'
84
+
85
+ And then execute:
86
+
87
+ $ bundle
88
+
89
+ Or install it yourself as:
90
+
91
+ $ gem install interchange
92
+
93
+
94
+ ## Contributing
95
+
96
+ 1. Fork it
97
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
98
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
99
+ 4. Push to the branch (`git push origin my-new-feature`)
100
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
8
+ task default: :test
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'interchange/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "interchange"
8
+ spec.version = Interchange::VERSION
9
+ spec.authors = ["ahawkins"]
10
+ spec.email = ["adam@hawkins.io"]
11
+ spec.description = %q{Define interfaces for interchangable objects & interfaces}
12
+ spec.summary = %q{}
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
+ end
@@ -0,0 +1,117 @@
1
+ require "interchange/version"
2
+
3
+ class Interchange < Module
4
+ class UnregisteredError < StandardError
5
+ def initialize(name)
6
+ super "#{name} is not a registered implementation"
7
+ end
8
+ end
9
+
10
+ class ImplementationMissingError < StandardError
11
+ def initialize(object, method)
12
+ super "The #{object.class} does not respond to #{method}"
13
+ end
14
+ end
15
+
16
+ class NotAvailableError < StandardError
17
+ def initialize(implementation)
18
+ super "#{implementation} currently down"
19
+ end
20
+ end
21
+
22
+ class NullImplementation
23
+ def up?
24
+ true
25
+ end
26
+
27
+ def respond_to?(*args)
28
+ true
29
+ end
30
+
31
+ def method_missing(name, *args, &block)
32
+ args
33
+ end
34
+ end
35
+
36
+ class DownImplementation < NullImplementation
37
+ def up?
38
+ false
39
+ end
40
+ end
41
+
42
+ module Methods
43
+ def register(name, implementation)
44
+ implementations[name] = implementation
45
+ end
46
+
47
+ def use(name)
48
+ @implementation = implementations.fetch name do
49
+ raise UnregisteredError, name
50
+ end
51
+ end
52
+
53
+ def with(name)
54
+ original = implementation
55
+ use name
56
+ result = yield self
57
+ @implementation = original
58
+
59
+ result
60
+ end
61
+
62
+ def down?
63
+ !up?
64
+ end
65
+
66
+ def check
67
+ fail NotAvailableError, implementation unless up?
68
+ true
69
+ end
70
+
71
+ def implementations
72
+ @implementations ||= { }
73
+ end
74
+
75
+ def implementation
76
+ @implementation
77
+ end
78
+ end
79
+
80
+ module DefaultImplementationsForInstances
81
+ def initialize(*args)
82
+ super
83
+ register :null, NullImplementation.new
84
+ register :down, DownImplementation.new
85
+ use :null
86
+ end
87
+ end
88
+
89
+ def included(base)
90
+ base.send :include, DefaultImplementationsForInstances
91
+ end
92
+
93
+ def extended(klass)
94
+ klass.register :null, NullImplementation.new
95
+ klass.register :down, DownImplementation.new
96
+ klass.use :null
97
+ end
98
+
99
+ def initialize(*methods)
100
+ module_eval do
101
+ include Methods
102
+ end
103
+
104
+ define_delegate_method :up?
105
+
106
+ methods.each do |method|
107
+ define_delegate_method method
108
+ end
109
+ end
110
+
111
+ def define_delegate_method(method)
112
+ define_method method do |*args, &block|
113
+ raise ImplementationMissingError.new(implementation, method) unless implementation.respond_to? method
114
+ implementation.send method, *args, &block
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,3 @@
1
+ class Interchange < Module
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,166 @@
1
+ require_relative 'test_helper'
2
+
3
+ class AcceptanceTest < MiniTest::Unit::TestCase
4
+ def build(&block)
5
+ Class.new &block
6
+ end
7
+
8
+ def test_use_raises_an_error_if_unknown_implementation
9
+ service = build do
10
+ include Interchange.new
11
+ end.new
12
+
13
+ assert_raises Interchange::UnregisteredError do
14
+ service.use :foo
15
+ end
16
+ end
17
+
18
+ def test_raises_an_error_if_method_not_implemented
19
+ service = build do
20
+ include Interchange.new(:add)
21
+ end.new
22
+
23
+ implementation = Class.new
24
+
25
+ service.register :test, implementation.new
26
+ service.use :test
27
+
28
+ assert_raises Interchange::ImplementationMissingError do
29
+ service.add 1, 2
30
+ end
31
+ end
32
+
33
+ def test_delegates_work_to_selected_implementation
34
+ service = build do
35
+ include Interchange.new(:add)
36
+ end.new
37
+
38
+ implementation = Class.new do
39
+ def add(*numbers)
40
+ numbers.inject(&:+)
41
+ end
42
+ end
43
+
44
+ service.register :test, implementation.new
45
+ service.use :test
46
+
47
+ assert_equal 3, service.add(1, 2)
48
+ end
49
+
50
+ def test_defines_an_up_query_method
51
+ service = build do
52
+ include Interchange.new(:add)
53
+ end.new
54
+
55
+ implementation = Class.new do
56
+ def up?
57
+ true
58
+ end
59
+ end
60
+
61
+ service.register :test, implementation.new
62
+ service.use :test
63
+
64
+ assert service.up?
65
+ refute service.down?
66
+ end
67
+
68
+ def test_generates_a_null_implementation_that_returns_the_arguments_by_default
69
+ service = build do
70
+ include Interchange.new(:add)
71
+ end.new
72
+
73
+ assert_equal [1,2], service.add(1,2)
74
+ end
75
+
76
+ def test_generates_a_down_implementation
77
+ service = build do
78
+ include Interchange.new(:add)
79
+ end.new
80
+
81
+ service.use :down
82
+ assert service.down?
83
+ end
84
+
85
+ def test_down_implementation_works_with_extend
86
+ service = build do
87
+ extend Interchange.new(:add)
88
+ end
89
+
90
+ service.use :down
91
+ assert service.down?
92
+ end
93
+
94
+ def test_can_check_to_see_if_services_are_up
95
+ service = build do
96
+ include Interchange.new(:foo)
97
+ end.new
98
+
99
+ service.use :down
100
+
101
+ assert_raises Interchange::NotAvailableError do
102
+ service.check
103
+ end
104
+ end
105
+
106
+ def test_can_implement_multiple_methods_at_once
107
+ service = build do
108
+ include Interchange.new(:foo, :bar)
109
+ end.new
110
+
111
+ implementation = Class.new do
112
+ def foo
113
+ :foo
114
+ end
115
+
116
+ def bar
117
+ :bar
118
+ end
119
+ end
120
+
121
+ service.register :test, implementation.new
122
+ service.use :test
123
+
124
+ assert_equal :foo, service.foo
125
+ assert_equal :bar, service.bar
126
+ end
127
+
128
+ def test_null_implementation_works_with_extend
129
+ service = build do
130
+ extend Interchange.new(:add)
131
+ end
132
+
133
+ assert_equal [1,2], service.add(1,2)
134
+ end
135
+
136
+ def test_can_use_one_implementation_temporarily
137
+ implementation_a = Class.new do
138
+ def number
139
+ 5
140
+ end
141
+ end.new
142
+
143
+ implementation_b = Class.new do
144
+ def number
145
+ 1
146
+ end
147
+ end.new
148
+
149
+ service = build do
150
+ include Interchange.new(:number)
151
+ end.new
152
+
153
+ service.register :five, implementation_a
154
+ service.register :one, implementation_b
155
+ service.use :one
156
+
157
+ assert_equal 1, service.number
158
+
159
+ number = service.with(:five) do |service|
160
+ service.number
161
+ end
162
+
163
+ assert_equal 5, number
164
+ assert_equal 1, service.number
165
+ end
166
+ end
@@ -0,0 +1,3 @@
1
+ require 'bundler/setup'
2
+ require 'interchange'
3
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: interchange
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ahawkins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-05 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
+ description: Define interfaces for interchangable objects & interfaces
42
+ email:
43
+ - adam@hawkins.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".travis.yml"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - interchange.gemspec
55
+ - lib/interchange.rb
56
+ - lib/interchange/version.rb
57
+ - test/acceptance_test.rb
58
+ - test/test_helper.rb
59
+ homepage: ''
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.2.2
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: ''
83
+ test_files:
84
+ - test/acceptance_test.rb
85
+ - test/test_helper.rb