substation 0.0.10.beta2 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -3
- data/Changelog.md +99 -24
- data/Gemfile +8 -1
- data/Gemfile.devtools +37 -21
- data/Guardfile +1 -1
- data/README.md +118 -46
- data/TODO.md +1 -0
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/reek.yml +41 -16
- data/config/rubocop.yml +44 -0
- data/config/yardstick.yml +1 -1
- data/lib/substation.rb +41 -5
- data/lib/substation/chain.rb +73 -84
- data/lib/substation/chain/definition.rb +147 -0
- data/lib/substation/chain/dsl.rb +150 -112
- data/lib/substation/chain/dsl/config.rb +55 -0
- data/lib/substation/chain/dsl/module_builder.rb +84 -0
- data/lib/substation/dispatcher.rb +20 -229
- data/lib/substation/dsl/guard.rb +96 -0
- data/lib/substation/dsl/registry.rb +181 -0
- data/lib/substation/environment.rb +126 -23
- data/lib/substation/environment/dsl.rb +31 -12
- data/lib/substation/processor.rb +238 -7
- data/lib/substation/processor/builder.rb +26 -0
- data/lib/substation/processor/config.rb +24 -0
- data/lib/substation/processor/evaluator.rb +66 -42
- data/lib/substation/processor/evaluator/handler.rb +54 -0
- data/lib/substation/processor/evaluator/result.rb +24 -0
- data/lib/substation/processor/executor.rb +46 -0
- data/lib/substation/processor/nest.rb +40 -0
- data/lib/substation/processor/transformer.rb +46 -0
- data/lib/substation/processor/wrapper.rb +16 -5
- data/lib/substation/request.rb +29 -27
- data/lib/substation/response.rb +19 -34
- data/lib/substation/response/api.rb +37 -0
- data/lib/substation/response/exception.rb +20 -0
- data/lib/substation/response/exception/output.rb +59 -0
- data/lib/substation/response/failure.rb +11 -0
- data/lib/substation/response/success.rb +11 -0
- data/lib/substation/version.rb +3 -1
- data/spec/demo/core.rb +64 -0
- data/spec/demo/core/action.rb +49 -0
- data/spec/demo/core/action/create_person.rb +28 -0
- data/spec/demo/core/errors.rb +16 -0
- data/spec/demo/core/facade.rb +38 -0
- data/spec/demo/core/handler.rb +21 -0
- data/spec/demo/core/handler/acceptor.rb +47 -0
- data/spec/demo/core/handler/authenticator.rb +36 -0
- data/spec/demo/core/handler/authorizer.rb +38 -0
- data/spec/demo/core/input.rb +15 -0
- data/spec/demo/core/observers.rb +10 -0
- data/spec/demo/core/validator.rb +21 -0
- data/spec/demo/domain/actor.rb +29 -0
- data/spec/demo/domain/dto/person.rb +18 -0
- data/spec/demo/domain/environment.rb +55 -0
- data/spec/demo/domain/storage.rb +49 -0
- data/spec/demo/web.rb +26 -0
- data/spec/demo/web/errors.rb +9 -0
- data/spec/demo/web/facade.rb +60 -0
- data/spec/demo/web/handler/deserializer.rb +36 -0
- data/spec/demo/web/presenter.rb +38 -0
- data/spec/demo/web/presenter/person.rb +19 -0
- data/spec/demo/web/renderer.rb +45 -0
- data/spec/demo/web/sanitizer.rb +35 -0
- data/spec/demo/web/sanitizer/person.rb +20 -0
- data/spec/demo/web/views.rb +28 -0
- data/spec/integration/demo/core_spec.rb +97 -0
- data/spec/integration/demo/web_spec.rb +114 -0
- data/spec/shared/context/integration/demo.rb +33 -0
- data/spec/shared/context/unit/chain.rb +13 -0
- data/spec/shared/context/unit/processor.rb +58 -0
- data/spec/shared/context/unit/request.rb +8 -0
- data/spec/shared/examples/integration/demo.rb +35 -0
- data/spec/shared/examples/unit/processor.rb +72 -0
- data/spec/spec_helper.rb +52 -23
- data/spec/unit/substation/chain/definition_spec.rb +141 -0
- data/spec/unit/substation/chain/dsl/config/dsl_module_spec.rb +13 -0
- data/spec/unit/substation/chain/dsl/config/registry_spec.rb +13 -0
- data/spec/unit/substation/chain/dsl/config_spec.rb +18 -0
- data/spec/unit/substation/chain/dsl/module_builder_spec.rb +77 -0
- data/spec/unit/substation/chain/dsl_spec.rb +175 -0
- data/spec/unit/substation/chain_spec.rb +303 -0
- data/spec/unit/substation/dispatcher_spec.rb +68 -0
- data/spec/unit/substation/dsl/guard_spec.rb +72 -0
- data/spec/unit/substation/dsl/registry_spec.rb +181 -0
- data/spec/unit/substation/environment/dsl_spec.rb +156 -0
- data/spec/unit/substation/environment_spec.rb +259 -0
- data/spec/unit/substation/processor/builder_spec.rb +21 -0
- data/spec/unit/substation/processor/config_spec.rb +40 -0
- data/spec/unit/substation/processor/evaluator/handler_spec.rb +20 -0
- data/spec/unit/substation/processor/evaluator/pivot_spec.rb +42 -0
- data/spec/unit/substation/processor/evaluator/request_spec.rb +11 -0
- data/spec/unit/substation/processor/evaluator/result/failure_spec.rb +14 -0
- data/spec/unit/substation/processor/evaluator/result/success_spec.rb +14 -0
- data/spec/unit/substation/processor/evaluator/result_spec.rb +13 -0
- data/spec/unit/substation/processor/evaluator_spec.rb +18 -0
- data/spec/unit/substation/processor/executor/null_spec.rb +25 -0
- data/spec/unit/substation/processor/executor_spec.rb +32 -0
- data/spec/unit/substation/processor/fallible_spec.rb +24 -0
- data/spec/unit/substation/processor/incoming_spec.rb +17 -0
- data/spec/unit/substation/processor/nest/incoming_spec.rb +56 -0
- data/spec/unit/substation/processor/nest_spec.rb +6 -0
- data/spec/unit/substation/processor/outgoing_spec.rb +47 -0
- data/spec/unit/substation/processor/transformer/incoming_spec.rb +17 -0
- data/spec/unit/substation/processor/transformer/outgoing_spec.rb +17 -0
- data/spec/unit/substation/processor/wrapper/incoming_spec.rb +15 -0
- data/spec/unit/substation/processor/wrapper/outgoing_spec.rb +15 -0
- data/spec/unit/substation/processor/wrapper_spec.rb +24 -0
- data/spec/unit/substation/processor_spec.rb +68 -0
- data/spec/unit/substation/request_spec.rb +70 -0
- data/spec/unit/substation/response/api_spec.rb +22 -0
- data/spec/unit/substation/response/exception/output_spec.rb +46 -0
- data/spec/unit/substation/response/exception_spec.rb +25 -0
- data/spec/unit/substation/response/failure_spec.rb +25 -0
- data/spec/unit/substation/response/success_spec.rb +24 -0
- data/spec/unit/substation/response_spec.rb +73 -0
- data/substation.gemspec +7 -6
- metadata +157 -67
- checksums.yaml +0 -7
- data/TODO +0 -0
- data/lib/substation/observer.rb +0 -66
- data/lib/substation/processor/pivot.rb +0 -25
- data/lib/substation/utils.rb +0 -68
- data/spec/integration/substation/dispatcher/call_spec.rb +0 -260
- data/spec/unit/substation/chain/call_spec.rb +0 -63
- data/spec/unit/substation/chain/dsl/builder/class_methods/call_spec.rb +0 -19
- data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +0 -21
- data/spec/unit/substation/chain/dsl/builder/failure_chain_spec.rb +0 -30
- data/spec/unit/substation/chain/dsl/chain_spec.rb +0 -15
- data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +0 -24
- data/spec/unit/substation/chain/dsl/initialize_spec.rb +0 -19
- data/spec/unit/substation/chain/dsl/processors_spec.rb +0 -42
- data/spec/unit/substation/chain/dsl/use_spec.rb +0 -14
- data/spec/unit/substation/chain/each_spec.rb +0 -46
- data/spec/unit/substation/chain/incoming/result_spec.rb +0 -21
- data/spec/unit/substation/chain/outgoing/call_spec.rb +0 -25
- data/spec/unit/substation/chain/outgoing/result_spec.rb +0 -21
- data/spec/unit/substation/dispatcher/action/call_spec.rb +0 -23
- data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +0 -61
- data/spec/unit/substation/dispatcher/action_names_spec.rb +0 -14
- data/spec/unit/substation/dispatcher/call_spec.rb +0 -47
- data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +0 -20
- data/spec/unit/substation/environment/chain_spec.rb +0 -50
- data/spec/unit/substation/environment/class_methods/build_spec.rb +0 -11
- data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +0 -18
- data/spec/unit/substation/environment/dsl/register_spec.rb +0 -14
- data/spec/unit/substation/environment/dsl/registry_spec.rb +0 -19
- data/spec/unit/substation/observer/chain/call_spec.rb +0 -26
- data/spec/unit/substation/observer/class_methods/coerce_spec.rb +0 -33
- data/spec/unit/substation/observer/null/call_spec.rb +0 -12
- data/spec/unit/substation/processor/evaluator/call_spec.rb +0 -49
- data/spec/unit/substation/processor/pivot/call_spec.rb +0 -17
- data/spec/unit/substation/processor/wrapper/call_spec.rb +0 -20
- data/spec/unit/substation/request/env_spec.rb +0 -14
- data/spec/unit/substation/request/error_spec.rb +0 -15
- data/spec/unit/substation/request/input_spec.rb +0 -14
- data/spec/unit/substation/request/success_spec.rb +0 -15
- data/spec/unit/substation/response/env_spec.rb +0 -16
- data/spec/unit/substation/response/failure/success_predicate_spec.rb +0 -15
- data/spec/unit/substation/response/input_spec.rb +0 -16
- data/spec/unit/substation/response/output_spec.rb +0 -16
- data/spec/unit/substation/response/request_spec.rb +0 -16
- data/spec/unit/substation/response/success/success_predicate_spec.rb +0 -15
- data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +0 -46
- data/spec/unit/substation/utils/class_methods/const_get_spec.rb +0 -46
- data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +0 -20
data/.travis.yml
CHANGED
@@ -6,9 +6,13 @@ rvm:
|
|
6
6
|
- 1.9.3
|
7
7
|
- 2.0.0
|
8
8
|
- ruby-head
|
9
|
-
-
|
10
|
-
- jruby-head
|
11
|
-
- rbx-19mode
|
9
|
+
- rbx
|
12
10
|
matrix:
|
11
|
+
include:
|
12
|
+
- rvm: jruby-19mode
|
13
|
+
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
14
|
+
- rvm: jruby-head
|
15
|
+
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
13
16
|
allow_failures:
|
14
17
|
- rvm: ruby-head
|
18
|
+
- rvm: jruby-head
|
data/Changelog.md
CHANGED
@@ -1,40 +1,115 @@
|
|
1
|
-
# v0.0.
|
1
|
+
# v0.0.10 2014-08-11
|
2
2
|
|
3
|
-
*
|
3
|
+
* This release is full of breaking changes and new features. The README is still out of
|
4
|
+
date too, but I need to get this out of the door. For a complete list of changes, have
|
5
|
+
a look at the diffs linked at the end of this section.
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
* Add the possibility to nest chains inside others
|
8
|
+
* Support decomposing/composing state before/after it is sent to handlers
|
9
|
+
* Make all handlers observable
|
10
|
+
* Support building on top of existing environments
|
11
|
+
* Rename Chain#{failure_chain => exception_chain}
|
12
|
+
* Store a chain's name in its definition
|
13
|
+
* Expose Dispatcher#include?(name)
|
14
|
+
* Make Chain::DSL accept an optional block
|
15
|
+
|
16
|
+
This allows for easy and syntactically nice
|
17
|
+
failure chain replacement when its desired
|
18
|
+
to replace/augment any of the failure chains
|
19
|
+
registered for processors within the chain to
|
20
|
+
merge.
|
21
|
+
|
22
|
+
[Compare v0.0.9..v0.0.10](https://github.com/snusnu/substation/compare/v0.0.9...v0.0.10)
|
23
|
+
|
24
|
+
# v0.0.9 2013-07-10
|
25
|
+
|
26
|
+
* [BREAKING CHANGE] Refactor `Substation::Processor` classes.
|
27
|
+
|
28
|
+
* Renamed `Substation::Processor::Evaluator` to `Substation::Processor::Evaluator::Data`.
|
29
|
+
* Renamed `Substation::Processor::Pivot` to `Substation::Processor::Evaluator::Pivot`.
|
30
|
+
* Added `Substation::Processor::Evaluator::Request` which passes the complete `Request` instance on to the handler.
|
31
|
+
* Added `Substation::Processor::Transformer` to transform `Response#output` into any other object.
|
32
|
+
|
33
|
+
* [feature] Make the dispatched name available in `Request#name`.
|
34
|
+
|
35
|
+
* [feature] Support (re)definining failure chains for `Substation::Processor::Fallible` processors.
|
36
|
+
|
37
|
+
module Demo
|
38
|
+
ENV = Substation::Environment.build do
|
39
|
+
register :validate, Substation::Processor::Evaluator::Data
|
40
|
+
register :call, Substation::Processor::Evaluator::Pivot
|
41
|
+
register :wrap, Substation::Processor::Wrapper
|
42
|
+
register :render, Substation::Processor::Transformer
|
43
|
+
end
|
44
|
+
|
45
|
+
class Error
|
46
|
+
attr_reader :data
|
47
|
+
def initialize(data)
|
48
|
+
@data = data
|
49
|
+
end
|
9
50
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@data = data
|
51
|
+
ValidationError = Class.new(self)
|
52
|
+
ApplicationError = Class.new(self)
|
53
|
+
InternalError = Class.new(self)
|
14
54
|
end
|
15
55
|
|
16
|
-
|
56
|
+
module App
|
57
|
+
VALIDATION_ERROR = Demo::ENV.chain { wrap Error::ValidationError }
|
58
|
+
APPLICATION_ERROR = Demo::ENV.chain { wrap Error::ApplicationError }
|
59
|
+
|
60
|
+
SOME_ACTION = Demo::ENV.chain do
|
61
|
+
validate Vanguard::Validator, VALIDATION_ERROR
|
62
|
+
call Some::Action, APPLICATION_ERROR
|
63
|
+
end
|
17
64
|
end
|
18
|
-
end
|
19
65
|
|
20
|
-
|
21
|
-
|
22
|
-
|
66
|
+
module Web
|
67
|
+
VALIDATION_ERROR = Demo::ENV.chain(App::VALIDATION_ERROR) do
|
68
|
+
render Renderer::ValidationError
|
69
|
+
end
|
70
|
+
|
71
|
+
APPLICATION_ERROR = Demo::ENV.chain(App::APPLICATION_ERROR) do
|
72
|
+
render Renderer::ApplicationError
|
73
|
+
end
|
74
|
+
|
75
|
+
# in case of success, returns an instance of Views::Person
|
76
|
+
# in case of validation failure, renders using Renderer::ValidationError
|
77
|
+
# in case of internal error, renders using Renderer::InternalError
|
78
|
+
SOME_ACTION = Demo::ENV.chain(App::SOME_ACTION) do
|
79
|
+
failure_chain :validate, VALIDATION_ERROR
|
80
|
+
failure_chain :call, INTERNAL_ERROR
|
81
|
+
wrap Presenters::Person
|
82
|
+
wrap Views::ShowPerson
|
83
|
+
end
|
23
84
|
end
|
24
|
-
call Some::Action
|
25
|
-
wrap Some::Presenter
|
26
85
|
end
|
27
86
|
|
28
|
-
|
29
|
-
|
30
|
-
|
87
|
+
* [feature] Support (re)defining chain specific failure chains in case of uncaught exceptions.
|
88
|
+
|
89
|
+
module Demo
|
31
90
|
|
32
|
-
|
33
|
-
|
91
|
+
module App
|
92
|
+
INTERNAL_ERROR = Demo::ENV.chain { wrap Error::InternalError }
|
93
|
+
end
|
94
|
+
|
95
|
+
module Web
|
96
|
+
|
97
|
+
INTERNAL_ERROR = Demo::ENV.chain(App::INTERNAL_ERROR) do
|
98
|
+
render Renderer::InternalError
|
99
|
+
end
|
34
100
|
|
35
|
-
|
101
|
+
# The INTERNAL_ERROR chain will be called if an exception
|
102
|
+
# isn't rescued by the responsible handler
|
103
|
+
SOME_ACTION = Demo::ENV.chain(App::SOME_ACTION, INTERNAL_ERROR) do
|
104
|
+
failure_chain :validate, VALIDATION_ERROR
|
105
|
+
failure_chain :call, INTERNAL_ERROR
|
106
|
+
wrap Presenters::Person
|
107
|
+
wrap Views::ShowPerson
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
36
111
|
|
37
|
-
[Compare v0.0.8..
|
112
|
+
[Compare v0.0.8..v0.0.9](https://github.com/snusnu/substation/compare/v0.0.8...v0.0.9)
|
38
113
|
|
39
114
|
# v0.0.8 2013-06-19
|
40
115
|
|
data/Gemfile
CHANGED
@@ -2,7 +2,14 @@ source 'http://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
+
group :test do
|
6
|
+
gem 'multi_json', '~> 1.8.0'
|
7
|
+
gem 'ducktrap', '~> 0.0.2', :git => 'https://github.com/mbj/ducktrap', :branch => 'master'
|
8
|
+
gem 'vanguard', '~> 0.0.4', :git => 'https://github.com/mbj/vanguard', :branch => 'master'
|
9
|
+
gem 'anima', '~> 0.2.0'
|
10
|
+
end
|
11
|
+
|
5
12
|
group :development do
|
6
|
-
gem 'devtools', :git => 'https://github.com/rom-rb/devtools.git'
|
13
|
+
gem 'devtools', :git => 'https://github.com/rom-rb/devtools.git', :branch => 'master'
|
7
14
|
eval File.read('Gemfile.devtools')
|
8
15
|
end
|
data/Gemfile.devtools
CHANGED
@@ -1,47 +1,63 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
group :development do
|
4
|
-
gem 'rake',
|
5
|
-
gem 'rspec',
|
6
|
-
gem '
|
4
|
+
gem 'rake', '~> 10.3.2'
|
5
|
+
gem 'rspec', '~> 3.0.0'
|
6
|
+
gem 'rspec-its', '~> 1.0.1'
|
7
|
+
gem 'yard', '~> 0.8.7.4'
|
8
|
+
|
9
|
+
platform :rbx do
|
10
|
+
gem 'rubysl-singleton', '~> 2.0.0'
|
11
|
+
end
|
7
12
|
end
|
8
13
|
|
9
14
|
group :yard do
|
10
|
-
gem 'kramdown', '~> 1.
|
15
|
+
gem 'kramdown', '~> 1.3.3'
|
11
16
|
end
|
12
17
|
|
13
18
|
group :guard do
|
14
|
-
gem 'guard', '~>
|
15
|
-
gem 'guard-bundler', '~>
|
16
|
-
gem 'guard-rspec', '~>
|
17
|
-
gem 'guard-rubocop', '~>
|
18
|
-
gem 'guard-mutant', '~> 0.0.1'
|
19
|
+
gem 'guard', '~> 2.6.1'
|
20
|
+
gem 'guard-bundler', '~> 2.0.0'
|
21
|
+
gem 'guard-rspec', '~> 4.2.9'
|
22
|
+
gem 'guard-rubocop', '~> 1.1.0'
|
19
23
|
|
20
24
|
# file system change event handling
|
21
|
-
gem 'listen', '~>
|
25
|
+
gem 'listen', '~> 2.7.7'
|
22
26
|
gem 'rb-fchange', '~> 0.0.6', require: false
|
23
|
-
gem 'rb-fsevent', '~> 0.9.
|
24
|
-
gem 'rb-inotify', '~> 0.9.
|
27
|
+
gem 'rb-fsevent', '~> 0.9.4', require: false
|
28
|
+
gem 'rb-inotify', '~> 0.9.5', require: false
|
25
29
|
|
26
30
|
# notification handling
|
27
|
-
gem 'libnotify', '~> 0.8.
|
31
|
+
gem 'libnotify', '~> 0.8.3', require: false
|
28
32
|
gem 'rb-notifu', '~> 0.0.4', require: false
|
29
33
|
gem 'terminal-notifier-guard', '~> 1.5.3', require: false
|
30
34
|
end
|
31
35
|
|
32
36
|
group :metrics do
|
33
|
-
gem 'coveralls', '~> 0.
|
34
|
-
gem 'flay', '~> 2.
|
35
|
-
gem 'flog', '~> 4.
|
36
|
-
gem 'reek', '~> 1.3.
|
37
|
-
gem 'rubocop', '~> 0.
|
37
|
+
gem 'coveralls', '~> 0.7.0'
|
38
|
+
gem 'flay', '~> 2.5.0'
|
39
|
+
gem 'flog', '~> 4.2.1'
|
40
|
+
gem 'reek', '~> 1.3.7'
|
41
|
+
gem 'rubocop', '~> 0.23.0'
|
38
42
|
gem 'simplecov', '~> 0.7.1'
|
39
|
-
gem 'yardstick', '~> 0.9.
|
43
|
+
gem 'yardstick', '~> 0.9.9'
|
44
|
+
|
45
|
+
platforms :mri do
|
46
|
+
gem 'mutant', '~> 0.5.23'
|
47
|
+
gem 'mutant-rspec', '~> 0.5.21'
|
48
|
+
end
|
40
49
|
|
41
50
|
platforms :ruby_19, :ruby_20 do
|
42
|
-
gem 'mutant', git: 'https://github.com/mbj/mutant.git'
|
43
51
|
gem 'yard-spellcheck', '~> 0.1.5'
|
44
52
|
end
|
53
|
+
|
54
|
+
platform :rbx do
|
55
|
+
gem 'json', '~> 1.8.1'
|
56
|
+
gem 'racc', '~> 1.4.11'
|
57
|
+
gem 'rubysl-logger', '~> 2.0.0'
|
58
|
+
gem 'rubysl-open-uri', '~> 2.0.0'
|
59
|
+
gem 'rubysl-prettyprint', '~> 2.0.3'
|
60
|
+
end
|
45
61
|
end
|
46
62
|
|
47
63
|
group :benchmarks do
|
@@ -50,6 +66,6 @@ end
|
|
50
66
|
|
51
67
|
platform :jruby do
|
52
68
|
group :jruby do
|
53
|
-
gem 'jruby-openssl', '~> 0.
|
69
|
+
gem 'jruby-openssl', '~> 0.9.4'
|
54
70
|
end
|
55
71
|
end
|
data/Guardfile
CHANGED
@@ -4,7 +4,7 @@ guard :bundler do
|
|
4
4
|
watch('Gemfile')
|
5
5
|
end
|
6
6
|
|
7
|
-
guard :rspec, :all_on_start => false, :all_after_pass => false do
|
7
|
+
guard :rspec, :all_on_start => false, :all_after_pass => false, :cmd => 'bundle exec rspec --fail-fast --seed 1' do
|
8
8
|
# run all specs if the spec_helper or supporting files files are modified
|
9
9
|
watch('spec/spec_helper.rb') { 'spec/unit' }
|
10
10
|
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec/unit' }
|
data/README.md
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
[![Build Status](https://secure.travis-ci.org/snusnu/substation.png?branch=master)][travis]
|
5
5
|
[![Dependency Status](https://gemnasium.com/snusnu/substation.png)][gemnasium]
|
6
6
|
[![Code Climate](https://codeclimate.com/github/snusnu/substation.png)][codeclimate]
|
7
|
+
[![Inline docs](http://inch-ci.org/github/snusnu/substation.png)](http://inch-ci.org/github/snusnu/substation)
|
7
8
|
[![Coverage Status](https://coveralls.io/repos/snusnu/substation/badge.png?branch=master)][coveralls]
|
8
9
|
|
9
10
|
[gem]: https://rubygems.org/gems/substation
|
@@ -22,7 +23,8 @@ receive arbitrary input data which will be available in `request.input`.
|
|
22
23
|
Additionally, `request.env` contains an arbitrary object that
|
23
24
|
represents your application environment and will typically provide access
|
24
25
|
to useful things like a logger and probably some sort of storage engine
|
25
|
-
abstraction object.
|
26
|
+
abstraction object. Furthermore, `request.name` will contain the action
|
27
|
+
name the `Substation::Dispatcher` used when dispatching to an action.
|
26
28
|
|
27
29
|
The contract further specifies that every action must return an instance
|
28
30
|
of either `Substation::Response::Success` or
|
@@ -509,9 +511,9 @@ In a typical application scenario, a few things need to happen before an
|
|
509
511
|
actual use case (an action) can be invoked. These things will often
|
510
512
|
include the following steps (probably in that order).
|
511
513
|
|
514
|
+
* Input data sanitization
|
512
515
|
* Authentication
|
513
516
|
* Authorization
|
514
|
-
* Input data sanitization
|
515
517
|
* Input data validation
|
516
518
|
|
517
519
|
We only want to invoke our action if all those steps succeed. If any of
|
@@ -541,129 +543,198 @@ b) If you need to return JSON, you might just
|
|
541
543
|
* Pass the response data to some serializer object and dump it to JSON
|
542
544
|
|
543
545
|
To allow chaining all those steps in a declarative way, substation
|
544
|
-
provides an object called `Substation::Chain`.
|
545
|
-
|
546
|
-
|
547
|
-
1. `#call(Substation::Request) => Substation::Response`
|
548
|
-
2. `#result(Substation::Response) => Substation::Response`
|
549
|
-
|
550
|
-
You typically won't be calling `Substation::Chain#result` yourself, but
|
551
|
-
having it around, allows us to use chains in *incoming handlers*,
|
552
|
-
essentially nesting chains. This makes it possible to construct one
|
553
|
-
chain up until the pivot handler, and then reuse that same chain in one
|
554
|
-
usecase that takes the response and renders HTML, and in another that
|
555
|
-
renders JSON.
|
556
|
-
|
557
|
-
To construct a chain, you need to pass an enumerable of so called
|
558
|
-
handler objects to `Substation::Chain.new`. Handlers must support two
|
559
|
-
methods:
|
546
|
+
provides an object called `Substation::Chain`. To construct a chain, you
|
547
|
+
need to pass an enumerable of processors to `Substation::Chain#initialize`.
|
548
|
+
Processors must support three methods:
|
560
549
|
|
561
550
|
1. `#call(<Substation::Request, Substation::Response>) => Substation::Response`
|
562
551
|
2. `#result(Substation::Response) => <Substation::Request, Substation::Response>`
|
552
|
+
3. `#success?(Substation::Response) => Boolean`
|
563
553
|
|
564
|
-
### Incoming
|
554
|
+
### Incoming processors
|
565
555
|
|
566
556
|
All steps required *before* processing the action will potentially
|
567
557
|
produce a new, altered, `Substation::Request`. Therefore, the object
|
568
558
|
passed to `#call` must be an instance of `Substation::Request`.
|
569
559
|
|
570
560
|
Since `#call` must return a `Substation::Response` (because the chain
|
571
|
-
would halt and return that response in case calling
|
561
|
+
would halt and return that response in case calling `Processor#success?`
|
572
562
|
method would return `false`), we also need to implement `#result`
|
573
563
|
and have it return a `Substation::Request` instance that can be passed
|
574
564
|
on to the next handler.
|
575
565
|
|
576
|
-
The contract for incoming
|
566
|
+
The contract for incoming processors therefore is:
|
577
567
|
|
578
568
|
1. `#call(Substation::Request) => Substation::Response`
|
579
569
|
2. `#result(Substation::Response) => Substation::Request`
|
570
|
+
3. `#success?(Substation::Response) => Boolean`
|
580
571
|
|
581
|
-
By including the `Substation::
|
582
|
-
class, you'll get the following for free:
|
572
|
+
By including the `Substation::Processor::Incoming` module into your
|
573
|
+
processor class, you'll get the following for free:
|
583
574
|
|
584
575
|
```ruby
|
576
|
+
def initialize(name, handler, failure_chain)
|
577
|
+
@name, @handler, @failure_chain = name, handler, failure_chain
|
578
|
+
end
|
579
|
+
|
585
580
|
def result(response)
|
586
|
-
|
581
|
+
response.to_request
|
582
|
+
end
|
583
|
+
|
584
|
+
def success?(response)
|
585
|
+
response.success?
|
586
|
+
end
|
587
|
+
|
588
|
+
def with_failure_chain(chain)
|
589
|
+
self.class.new(name, handler, chain)
|
587
590
|
end
|
588
591
|
```
|
589
592
|
|
590
|
-
This shows that an incoming
|
593
|
+
This shows that an incoming processor can alter the incoming request in any
|
591
594
|
way that it wants to, as long as it returns the new request input data in
|
592
595
|
`Substation::Response#output` returned from `#call`.
|
593
596
|
|
594
|
-
|
597
|
+
Currently, `substation` provides the following incoming processors out
|
598
|
+
of the box:
|
599
|
+
|
600
|
+
* `Substation::Processor::Evaluator::Request` passes `request` to the handler
|
601
|
+
* `Substation::Processor::Evaluator::Data` passes `request.input` to the handler
|
602
|
+
|
603
|
+
### The pivot processor
|
595
604
|
|
596
605
|
Pivot is just another fancy name for the action in the context of a
|
597
|
-
chain. It's also the point where all subsequent
|
606
|
+
chain. It's also the point where all subsequent processors have to further
|
598
607
|
process the `Substation::Response` returned from invoking the action.
|
608
|
+
Therefore, the pivot processor is the last processor that expects a
|
609
|
+
`Substation::Request` as parameter to its `#call` method.
|
599
610
|
|
600
|
-
The contract for the pivot
|
611
|
+
The contract for the pivot processor therefore is:
|
601
612
|
|
602
613
|
1. `#call(Substation::Request) => Substation::Response`
|
603
614
|
2. `#result(Substation::Response) => Substation::Response`
|
615
|
+
3. `#success?(Substation::Response) => Boolean`
|
604
616
|
|
605
|
-
By including the `Substation::
|
617
|
+
By including the `Substation::Processor::Pivot` module into your handler
|
606
618
|
class, you'll get the following for free:
|
607
619
|
|
608
620
|
```ruby
|
621
|
+
def initialize(name, handler, failure_chain)
|
622
|
+
@name, @handler, @failure_chain = name, handler, failure_chain
|
623
|
+
end
|
624
|
+
|
609
625
|
def result(response)
|
610
626
|
response
|
611
627
|
end
|
628
|
+
|
629
|
+
def success?(response)
|
630
|
+
response.success?
|
631
|
+
end
|
632
|
+
|
633
|
+
def with_failure_chain(chain)
|
634
|
+
self.class.new(name, handler, chain)
|
635
|
+
end
|
612
636
|
```
|
613
637
|
|
614
|
-
This reflects the fact that a pivot
|
615
|
-
producing the "raw" response, returns it unaltered.
|
638
|
+
This reflects the fact that a pivot processor (since it's the one actually
|
639
|
+
producing the "raw" response, returns it unaltered).
|
640
|
+
|
641
|
+
The pivot processor is shipped with `substation` and is implemented by
|
642
|
+
`Substation::Processor::Evaluator::Pivot`.
|
616
643
|
|
617
|
-
### Outgoing
|
644
|
+
### Outgoing processors
|
618
645
|
|
619
646
|
All steps required *after* processing the action will potentially
|
620
647
|
produce a new, altered, `Substation::Response` instance to be returned.
|
621
648
|
Therefore the object passed to `#call` must be an instance of
|
622
|
-
`Substation::Response`. Since subsequent outgoing
|
649
|
+
`Substation::Response`. Since subsequent outgoing processors might further
|
623
650
|
process the response, `#result` must be implemented so that it returns a
|
624
|
-
`Substation::Response` object that can be passed on to the next
|
651
|
+
`Substation::Response` object that can be passed on to the next
|
652
|
+
processor.
|
625
653
|
|
626
|
-
The contract for outgoing
|
654
|
+
The contract for outgoing processors therefore is:
|
627
655
|
|
628
656
|
1. `#call(Substation::Response) => Substation::Response`
|
629
657
|
2. `#result(Substation::Response) => Substation::Response`
|
658
|
+
3. `#success?(Substation::Response) => true`
|
630
659
|
|
631
|
-
By including the `Substation::
|
632
|
-
class, you'll get the following for free:
|
660
|
+
By including the `Substation::Processor::Outgoing` module into your
|
661
|
+
processor class, you'll get the following for free:
|
633
662
|
|
634
663
|
```ruby
|
664
|
+
def initialize(name, handler)
|
665
|
+
@name, @handler = name, handler
|
666
|
+
end
|
667
|
+
|
635
668
|
def result(response)
|
636
669
|
response
|
637
670
|
end
|
671
|
+
|
672
|
+
def success?(response)
|
673
|
+
true
|
674
|
+
end
|
675
|
+
|
676
|
+
private
|
677
|
+
|
678
|
+
def respond_with(response, output)
|
679
|
+
response.class.new(response.request, output)
|
680
|
+
end
|
638
681
|
```
|
639
682
|
|
640
|
-
This shows that an outgoing
|
683
|
+
This shows that an outgoing processor's `#call` can do anything with
|
641
684
|
the `Substation::Response#output` it received, as long as it makes
|
642
685
|
sure to return a new response with the new output properly set.
|
643
686
|
|
687
|
+
Currently, `substation` provides the following outgoing processors out
|
688
|
+
of the box:
|
689
|
+
|
690
|
+
* `Substation::Processor::Wrapper` wraps `response.output` in a new handler instance
|
691
|
+
* `Substation::Processor::Transformer` transforms `response.output` using a new handler instance
|
692
|
+
|
693
|
+
### Handlers
|
694
|
+
|
695
|
+
You might have noticed the `handler` param passed to any processor's
|
696
|
+
`#initialize` method. Handlers are the actual objects performing your
|
697
|
+
application logic. Processors use these handlers to produce the data
|
698
|
+
they're supposed to "pipe through the chain".
|
699
|
+
|
700
|
+
The interface your handlers must implement should be familiar by now.
|
701
|
+
|
702
|
+
All handlers to be used with incoming processors must accept an instance
|
703
|
+
of `Substation::Request` as parameter to `#call`. Handlers to be used
|
704
|
+
with `Substation::Processor::Evaluator` subclasses must furthermore
|
705
|
+
return an object that responds to `#success?` and `#output`.
|
706
|
+
|
707
|
+
Note how the interface required for evaluator handler return values
|
708
|
+
matches the interface a `Substation::Response` exposes. This means that
|
709
|
+
the pivot processor can be (and is) implemented using the builtin
|
710
|
+
`Substation::Processor::Evaluator::Request` processor. The handler you
|
711
|
+
pass to the pivot processor is the object that actually implements your
|
712
|
+
application usecase, the action, and it's response gets evaluated.
|
713
|
+
|
714
|
+
All handlers to be used with outgoing processors must accept an instance
|
715
|
+
of `Substation::Response` as parameter to `#call`. They can do whatever
|
716
|
+
they want with the passed in response, but they must make sure to return
|
717
|
+
another instance of `Substation::Response`. To help with this, outgoing
|
718
|
+
processors provide the `#respond_with(response, data)` method that
|
719
|
+
you'll typically call to return the response value for `#call`.
|
720
|
+
|
644
721
|
### Example
|
645
722
|
|
646
723
|
[substation-demo](https://github.com/snusnu/substation-demo) implements a
|
647
724
|
simple web application using `Substation::Chain`.
|
648
725
|
|
649
|
-
The demo
|
726
|
+
The demo uses a few of the above mentioned *incoming processors*
|
650
727
|
for
|
651
728
|
|
652
729
|
* [Sanitization](https://github.com/snusnu/substation-demo/blob/master/demo/web/sanitizers.rb) using [ducktrap](https://github.com/mbj/ducktrap)
|
653
730
|
* [Validation](https://github.com/snusnu/substation-demo/blob/master/demo/validators.rb) using [vanguard](https://github.com/mbj/vanguard)
|
654
731
|
|
655
|
-
and some simple *outgoing
|
732
|
+
and some simple *outgoing processors* for
|
656
733
|
|
657
734
|
* Wrapping response output in a
|
658
735
|
[presenter](https://github.com/snusnu/substation-demo/blob/master/demo/web/presenters.rb)
|
659
736
|
* [Serializing](https://github.com/snusnu/substation-demo/blob/master/demo/web/serializers.rb) response output to JSON
|
660
737
|
|
661
|
-
The
|
662
|
-
[handlers](https://github.com/snusnu/substation-demo/blob/master/demo/web/processors.rb)
|
663
|
-
are called *processors* in that app, and encapsulate the actual handler
|
664
|
-
performing the job. That's a common pattern, because you typically will
|
665
|
-
have to adapt to the interface your actual handlers provide.
|
666
|
-
|
667
738
|
Have a look at the base
|
668
739
|
[actions](https://github.com/snusnu/substation-demo/blob/master/demo/web/actions.rb)
|
669
740
|
that are then used to either produce
|
@@ -679,6 +750,7 @@ Finally it's all hooked up behind a few
|
|
679
750
|
|
680
751
|
* [snusnu](https://github.com/snusnu)
|
681
752
|
* [mbj](https://github.com/mbj)
|
753
|
+
* [indrekj](https://github.com/indrekj)
|
682
754
|
|
683
755
|
## Contributing
|
684
756
|
|