substation 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +37 -0
- data/.rspec +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +8 -0
- data/Gemfile.devtools +60 -0
- data/Guardfile +18 -0
- data/LICENSE +20 -0
- data/README.md +322 -0
- data/Rakefile +6 -0
- data/TODO +0 -0
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +103 -0
- data/config/yardstick.yml +2 -0
- data/lib/substation/dispatcher.rb +262 -0
- data/lib/substation/observer.rb +66 -0
- data/lib/substation/request.rb +69 -0
- data/lib/substation/response.rb +178 -0
- data/lib/substation/support/utils.rb +68 -0
- data/lib/substation/version.rb +4 -0
- data/lib/substation.rb +40 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/unit/substation/dispatcher/action/call_spec.rb +23 -0
- data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +46 -0
- data/spec/unit/substation/dispatcher/action_names_spec.rb +13 -0
- data/spec/unit/substation/dispatcher/call_spec.rb +47 -0
- data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +18 -0
- data/spec/unit/substation/observer/chain/call_spec.rb +26 -0
- data/spec/unit/substation/observer/class_methods/coerce_spec.rb +33 -0
- data/spec/unit/substation/observer/null/call_spec.rb +12 -0
- data/spec/unit/substation/request/env_spec.rb +14 -0
- data/spec/unit/substation/request/error_spec.rb +15 -0
- data/spec/unit/substation/request/input_spec.rb +14 -0
- data/spec/unit/substation/request/success_spec.rb +15 -0
- data/spec/unit/substation/response/env_spec.rb +16 -0
- data/spec/unit/substation/response/failure/success_predicate_spec.rb +15 -0
- data/spec/unit/substation/response/input_spec.rb +16 -0
- data/spec/unit/substation/response/output_spec.rb +16 -0
- data/spec/unit/substation/response/success/success_predicate_spec.rb +15 -0
- data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +34 -0
- data/spec/unit/substation/utils/class_methods/const_get_spec.rb +46 -0
- data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +20 -0
- data/substation.gemspec +25 -0
- metadata +177 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'substation'
|
2
|
+
require 'devtools/spec_helper'
|
3
|
+
|
4
|
+
module Spec
|
5
|
+
|
6
|
+
def self.response_data
|
7
|
+
:data
|
8
|
+
end
|
9
|
+
|
10
|
+
class Observer
|
11
|
+
def self.call(response)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Action
|
16
|
+
class Success
|
17
|
+
def self.call(request)
|
18
|
+
request.success(Spec.response_data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Failure
|
23
|
+
def self.call(request)
|
24
|
+
request.error(Spec.response_data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
include Substation
|
32
|
+
|
33
|
+
RSpec.configure do |config|
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Dispatcher::Action, '#call' do
|
6
|
+
|
7
|
+
subject { object.call(request) }
|
8
|
+
|
9
|
+
let(:object) { described_class.new(klass, observer) }
|
10
|
+
let(:klass) { mock }
|
11
|
+
let(:observer) { mock }
|
12
|
+
let(:request) { Request.new(env, input) }
|
13
|
+
let(:env) { mock }
|
14
|
+
let(:input) { mock }
|
15
|
+
let(:response) { mock }
|
16
|
+
|
17
|
+
before do
|
18
|
+
klass.should_receive(:call).with(request).and_return(response)
|
19
|
+
observer.should_receive(:call).with(response)
|
20
|
+
end
|
21
|
+
|
22
|
+
it { should eql(response) }
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Dispatcher::Action, '.coerce' do
|
6
|
+
|
7
|
+
subject { described_class.coerce(config) }
|
8
|
+
|
9
|
+
let(:action) { Spec::Action::Success }
|
10
|
+
let(:coerced) { Dispatcher::Action.new(action, observer) }
|
11
|
+
|
12
|
+
context "when coercion is possible" do
|
13
|
+
|
14
|
+
let(:config) {{
|
15
|
+
:action => action,
|
16
|
+
:observer => observer_value
|
17
|
+
}}
|
18
|
+
|
19
|
+
before do
|
20
|
+
Observer.should_receive(:coerce).with(observer_value).and_return(observer)
|
21
|
+
Utils.should_receive(:coerce_callable).with(action).and_return(action)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with an action and an observer' do
|
25
|
+
let(:observer_value) { observer }
|
26
|
+
let(:observer) { Spec::Observer }
|
27
|
+
|
28
|
+
it { should eql(coerced) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with an action and no observer' do
|
32
|
+
let(:observer_value) { nil }
|
33
|
+
let(:observer) { Observer::NULL }
|
34
|
+
|
35
|
+
it { should eql(coerced) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with no action' do
|
40
|
+
let(:config) { {} }
|
41
|
+
|
42
|
+
specify {
|
43
|
+
expect { subject }.to raise_error(described_class::MissingHandlerError)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Dispatcher, '#action_names' do
|
6
|
+
|
7
|
+
subject { object.action_names }
|
8
|
+
|
9
|
+
let(:object) { described_class.new(config) }
|
10
|
+
let(:config) { { :test => { :action => Spec::Action::Success } } }
|
11
|
+
|
12
|
+
it { should eql(Set[ :test ]) }
|
13
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Dispatcher, '#call' do
|
6
|
+
|
7
|
+
subject { object.call(action_name, input, env) }
|
8
|
+
|
9
|
+
let(:object) { described_class.coerce(config) }
|
10
|
+
let(:config) { { 'test' => { 'action' => 'Spec::Action::Success' } } }
|
11
|
+
let(:request) { Request.new(env, input) }
|
12
|
+
let(:input) { mock }
|
13
|
+
let(:env) { mock }
|
14
|
+
|
15
|
+
let(:expected_response) do
|
16
|
+
Spec::Action::Success.call(request)
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when the action is registered' do
|
20
|
+
let(:action_name) { :test }
|
21
|
+
|
22
|
+
context 'without callbacks' do
|
23
|
+
it { should eql(expected_response) }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with observers' do
|
27
|
+
let(:config) {
|
28
|
+
config = super()
|
29
|
+
config['test']['observer'] = 'Spec::Observer'
|
30
|
+
config
|
31
|
+
}
|
32
|
+
|
33
|
+
it 'should call callback with response' do
|
34
|
+
Spec::Observer.should_receive(:call).with(expected_response)
|
35
|
+
should eql(expected_response)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when the action is not registered' do
|
41
|
+
let(:action_name) { :unknown }
|
42
|
+
|
43
|
+
specify do
|
44
|
+
expect { subject }.to raise_error(described_class::UnknownActionError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Dispatcher, '.coerce' do
|
6
|
+
|
7
|
+
subject { described_class.coerce(config) }
|
8
|
+
|
9
|
+
let(:config) {{
|
10
|
+
'test' => { 'action' => 'Spec::Action::Success' }
|
11
|
+
}}
|
12
|
+
|
13
|
+
let(:coerced) {{
|
14
|
+
:test => described_class::Action.coerce(:action => 'Spec::Action::Success')
|
15
|
+
}}
|
16
|
+
|
17
|
+
it { should eql(described_class.new(coerced)) }
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Observer::Chain, '#call' do
|
4
|
+
subject { object.call(response) }
|
5
|
+
|
6
|
+
let(:object) { Observer::Chain.new(observers) }
|
7
|
+
|
8
|
+
let(:response) { mock('Response') }
|
9
|
+
let(:observer_a) { mock('Observer A') }
|
10
|
+
let(:observer_b) { mock('Observer B') }
|
11
|
+
|
12
|
+
let(:observers) { [observer_a, observer_b] }
|
13
|
+
|
14
|
+
it_should_behave_like 'a command method'
|
15
|
+
|
16
|
+
before do
|
17
|
+
observer_a.stub(:call)
|
18
|
+
observer_b.stub(:call)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should call observers' do
|
22
|
+
observer_a.should_receive(:call).with(response)
|
23
|
+
observer_b.should_receive(:call).with(response)
|
24
|
+
subject
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Observer, '.coerce' do
|
4
|
+
|
5
|
+
subject { object.coerce(input) }
|
6
|
+
|
7
|
+
let(:object) { described_class }
|
8
|
+
|
9
|
+
context 'with nil input' do
|
10
|
+
let(:input) { nil }
|
11
|
+
|
12
|
+
it { should be(described_class::NULL) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'with array input' do
|
16
|
+
let(:input) { ['Spec::Observer', nil] }
|
17
|
+
|
18
|
+
let(:observers) { [Spec::Observer, described_class::NULL] }
|
19
|
+
|
20
|
+
it { should eql(described_class::Chain.new(observers)) }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with other input' do
|
24
|
+
let(:input) { mock }
|
25
|
+
let(:coerced) { mock }
|
26
|
+
|
27
|
+
before do
|
28
|
+
Utils.should_receive(:coerce_callable).with(input).and_return(coerced)
|
29
|
+
end
|
30
|
+
|
31
|
+
it { should eql(coerced) }
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Request, '#error' do
|
6
|
+
|
7
|
+
subject { object.error(output) }
|
8
|
+
|
9
|
+
let(:object) { described_class.new(env, input) }
|
10
|
+
let(:env) { mock }
|
11
|
+
let(:input) { mock }
|
12
|
+
let(:output) { mock }
|
13
|
+
|
14
|
+
it { should eql(Response::Failure.new(object, output)) }
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Request, '#success' do
|
6
|
+
|
7
|
+
subject { object.success(output) }
|
8
|
+
|
9
|
+
let(:object) { described_class.new(env, input) }
|
10
|
+
let(:env) { mock }
|
11
|
+
let(:input) { mock }
|
12
|
+
let(:output) { mock }
|
13
|
+
|
14
|
+
it { should eql(Response::Success.new(object, output)) }
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Response, '#env' do
|
6
|
+
|
7
|
+
subject { object.env }
|
8
|
+
|
9
|
+
let(:object) { Class.new(described_class).new(request, output) }
|
10
|
+
let(:request) { Request.new(env, input) }
|
11
|
+
let(:env) { mock }
|
12
|
+
let(:input) { mock }
|
13
|
+
let(:output) { mock }
|
14
|
+
|
15
|
+
it { should equal(env) }
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Response::Failure, '#success?' do
|
6
|
+
subject { object.success? }
|
7
|
+
|
8
|
+
let(:object) { described_class.new(request, output) }
|
9
|
+
let(:request) { Request.new(env, input) }
|
10
|
+
let(:env) { mock }
|
11
|
+
let(:input) { mock }
|
12
|
+
let(:output) { mock }
|
13
|
+
|
14
|
+
it { should be(false) }
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Response, '#input' do
|
6
|
+
|
7
|
+
subject { object.input }
|
8
|
+
|
9
|
+
let(:object) { Class.new(described_class).new(request, output) }
|
10
|
+
let(:request) { Request.new(env, input) }
|
11
|
+
let(:env) { mock }
|
12
|
+
let(:input) { mock }
|
13
|
+
let(:output) { mock }
|
14
|
+
|
15
|
+
it { should equal(input) }
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Response, '#output' do
|
6
|
+
|
7
|
+
subject { object.output }
|
8
|
+
|
9
|
+
let(:object) { Class.new(described_class).new(request, output) }
|
10
|
+
let(:request) { Request.new(env, input) }
|
11
|
+
let(:env) { mock }
|
12
|
+
let(:input) { mock }
|
13
|
+
let(:output) { mock }
|
14
|
+
|
15
|
+
it { should equal(output) }
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Response::Success, '#success?' do
|
6
|
+
subject { object.success? }
|
7
|
+
|
8
|
+
let(:object) { described_class.new(request, output) }
|
9
|
+
let(:request) { Request.new(env, input) }
|
10
|
+
let(:env) { mock }
|
11
|
+
let(:input) { mock }
|
12
|
+
let(:output) { mock }
|
13
|
+
|
14
|
+
it { should be(true) }
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Utils, '.coerce_callable' do
|
6
|
+
|
7
|
+
subject { described_class.coerce_callable(handler) }
|
8
|
+
|
9
|
+
context "with a String handler" do
|
10
|
+
let(:handler) { 'Spec::Action::Success' }
|
11
|
+
|
12
|
+
it { should be(Spec::Action::Success) }
|
13
|
+
end
|
14
|
+
|
15
|
+
context "with a Symbol handler" do
|
16
|
+
let(:handler) { :'Spec::Action::Success' }
|
17
|
+
|
18
|
+
it { should be(Spec::Action::Success) }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with a Proc handler" do
|
22
|
+
let(:handler) { Proc.new { |response| respone } }
|
23
|
+
|
24
|
+
it { should be(handler) }
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with an unsupported handler" do
|
28
|
+
let(:handler) { mock }
|
29
|
+
|
30
|
+
specify do
|
31
|
+
expect { subject }.to raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Utils, '.const_get' do
|
6
|
+
|
7
|
+
subject { described_class.const_get(name) }
|
8
|
+
|
9
|
+
context "with a toplevel constant" do
|
10
|
+
context "given as String" do
|
11
|
+
let(:name) { 'Substation' }
|
12
|
+
|
13
|
+
it { should == Substation }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "given as Symbol" do
|
17
|
+
let(:name) { :Substation }
|
18
|
+
|
19
|
+
it { should == Substation }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with a FQN toplevel constant" do
|
24
|
+
let(:name) { '::Substation' }
|
25
|
+
|
26
|
+
it { should == Substation }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with a nested constant" do
|
30
|
+
let(:name) { 'Substation::Request' }
|
31
|
+
|
32
|
+
it { should == Substation::Request }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "with a non-existant nested constant" do
|
36
|
+
let(:name) { 'Substation::Foo' }
|
37
|
+
|
38
|
+
before do
|
39
|
+
Substation.should_receive(:const_missing).with('Foo').and_raise(NameError)
|
40
|
+
end
|
41
|
+
|
42
|
+
specify do
|
43
|
+
expect { subject }.to raise_error(NameError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Utils, '.symbolize_keys' do
|
6
|
+
|
7
|
+
subject { described_class.symbolize_keys(hash) }
|
8
|
+
|
9
|
+
context "with no nested hash" do
|
10
|
+
let(:hash) { { 'foo' => 'bar' } }
|
11
|
+
|
12
|
+
it { should == { :foo => 'bar'} }
|
13
|
+
end
|
14
|
+
|
15
|
+
context "with a nested hash" do
|
16
|
+
let(:hash) { { 'foo' => { 'bar' => 'baz' } } }
|
17
|
+
|
18
|
+
it { should == { :foo => { :bar => 'baz'} } }
|
19
|
+
end
|
20
|
+
end
|
data/substation.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/substation/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "substation"
|
7
|
+
gem.version = Substation::VERSION.dup
|
8
|
+
gem.authors = [ "Martin Gamsjaeger (snusnu)" ]
|
9
|
+
gem.email = [ "gamsnjaga@gmail.com" ]
|
10
|
+
gem.description = "Implement application boundary interfaces with dedicated classes"
|
11
|
+
gem.summary = "Think of it as a domain level request router. It assumes that every usecase in your application has a name and is implemented in a dedicated action handler (class)."
|
12
|
+
gem.homepage = "https://github.com/snusnu/substation"
|
13
|
+
|
14
|
+
gem.require_paths = [ "lib" ]
|
15
|
+
gem.files = `git ls-files`.split("\n")
|
16
|
+
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
17
|
+
gem.extra_rdoc_files = %w[LICENSE README.md TODO]
|
18
|
+
|
19
|
+
gem.add_dependency 'adamantium', '~> 0.0.7'
|
20
|
+
gem.add_dependency 'equalizer', '~> 0.0.5'
|
21
|
+
gem.add_dependency 'abstract_type', '~> 0.0.5'
|
22
|
+
gem.add_dependency 'concord', '~> 0.0.3'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'bundler', '~> 1.3.5'
|
25
|
+
end
|