interchange 0.1.0

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.
@@ -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