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