substation 0.0.1 → 0.0.2
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/Changelog.md +12 -0
- data/README.md +21 -20
- data/config/flay.yml +1 -1
- data/lib/substation/dispatcher.rb +54 -37
- data/lib/substation/version.rb +1 -1
- data/spec/spec_helper.rb +19 -1
- data/spec/unit/substation/dispatcher/action_names_spec.rb +2 -1
- data/spec/unit/substation/dispatcher/call_spec.rb +2 -2
- data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +4 -2
- metadata +3 -2
data/Changelog.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# v0.0.2 2013-05-15
|
2
|
+
|
3
|
+
* [BREAKING CHANGE] Creating a dispatcher requires an application env (snusnu)
|
4
|
+
|
5
|
+
* Changes `Substation::Dispatcher.coerce(config)` to `Substation::Dispatcher.coerce(config, env)`
|
6
|
+
* Changes `Substation::Dispatcher#call(name, input, env)` to `Substation::Dispatcher#call(name, input)`
|
7
|
+
|
8
|
+
[Compare v0.0.1..v0.0.2](https://github.com/snusnu/substation/compare/v0.0.1...v0.0.2)
|
9
|
+
|
10
|
+
# v0.0.1 2013-05-14
|
11
|
+
|
12
|
+
First public release
|
data/README.md
CHANGED
@@ -4,11 +4,13 @@
|
|
4
4
|
[][travis]
|
5
5
|
[][gemnasium]
|
6
6
|
[][codeclimate]
|
7
|
+
[][coveralls]
|
7
8
|
|
8
9
|
[gem]: https://rubygems.org/gems/substation
|
9
10
|
[travis]: https://travis-ci.org/snusnu/substation
|
10
11
|
[gemnasium]: https://gemnasium.com/snusnu/substation
|
11
12
|
[codeclimate]: https://codeclimate.com/github/snusnu/substation
|
13
|
+
[coveralls]: https://coveralls.io/r/snusnu/substation
|
12
14
|
|
13
15
|
`substation` can be thought of as a domain level request router. It assumes
|
14
16
|
that every usecase in your application has a name and is implemented in a dedicated
|
@@ -29,9 +31,9 @@ addition to that, `response.success?` is available and will indicate
|
|
29
31
|
wether invoking the action was successful or not.
|
30
32
|
|
31
33
|
`Substation::Dispatcher` stores a mapping of action names to the actual
|
32
|
-
objects implementing the action
|
33
|
-
`Substation::Dispatcher#call(name, input
|
34
|
-
registered action. For example, a web application could map an http
|
34
|
+
objects implementing the action, as well as the application environment.
|
35
|
+
Clients can use `Substation::Dispatcher#call(name, input)` to dispatch to
|
36
|
+
any registered action. For example, a web application could map an http
|
35
37
|
route to a specific action name and pass relevant http params on to the
|
36
38
|
action.
|
37
39
|
|
@@ -191,14 +193,16 @@ For this purpose, we can instantiate a `Substation::Dispatcher` and hand
|
|
191
193
|
it a configuration hash that describes the various actions by giving
|
192
194
|
them a name, a class that's responsible for implementing the actual
|
193
195
|
usecase, and a list of `0..n` observers that should be invoked depending
|
194
|
-
on the action response.
|
196
|
+
on the action response. In addition to that, we must pass an instance of
|
197
|
+
the application's environment. More about application environments can
|
198
|
+
be found in the next paragraph.
|
195
199
|
|
196
200
|
An example configuration for an action without any observers:
|
197
201
|
|
198
202
|
```ruby
|
199
203
|
dispatcher = Substation::Dispatcher.coerce({
|
200
204
|
'some_use_case' => { 'action' => 'App::SomeUseCase' }
|
201
|
-
})
|
205
|
+
}, env)
|
202
206
|
```
|
203
207
|
|
204
208
|
An example configuration for an action with one observer:
|
@@ -209,7 +213,7 @@ dispatcher = Substation::Dispatcher.coerce({
|
|
209
213
|
'action' => 'App::SomeUseCase',
|
210
214
|
'observer' => 'App::SomeUseCaseObserver'
|
211
215
|
}
|
212
|
-
})
|
216
|
+
}, env)
|
213
217
|
```
|
214
218
|
|
215
219
|
An example configuration for an action with multiple observers:
|
@@ -223,7 +227,7 @@ dispatcher = Substation::Dispatcher.coerce({
|
|
223
227
|
'App::AnotherObserver'
|
224
228
|
]
|
225
229
|
}
|
226
|
-
})
|
230
|
+
}, env)
|
227
231
|
```
|
228
232
|
|
229
233
|
The above configuration examples are tailored towards being read from a
|
@@ -240,7 +244,7 @@ dispatcher = Substation::Dispatcher.coerce({
|
|
240
244
|
:action => App::SomeUseCase,
|
241
245
|
:observer => App::SomeUseCaseObserver
|
242
246
|
}
|
243
|
-
})
|
247
|
+
}, env)
|
244
248
|
```
|
245
249
|
|
246
250
|
An example configuration using symbol keys and procs for handlers:
|
@@ -251,7 +255,7 @@ dispatcher = Substation::Dispatcher.coerce({
|
|
251
255
|
:action => Proc.new { |request| request.success(:foo) },
|
252
256
|
:observer => Proc.new { |response| do_something }
|
253
257
|
}
|
254
|
-
})
|
258
|
+
}, env)
|
255
259
|
```
|
256
260
|
|
257
261
|
|
@@ -262,8 +266,8 @@ the course of performing a usecase (like a logger or a storage engine
|
|
262
266
|
abstraction), you can encapsulate these objects within an application
|
263
267
|
specific environment object, and send that along to every action.
|
264
268
|
|
265
|
-
Here's a simple example with an environment that encapsulates a logger
|
266
|
-
an artificial storage abstraction object
|
269
|
+
Here's a simple example with an environment that encapsulates a logger
|
270
|
+
and an artificial storage abstraction object.
|
267
271
|
|
268
272
|
The example builds on top of the application specific action baseclass
|
269
273
|
shown above:
|
@@ -272,13 +276,10 @@ shown above:
|
|
272
276
|
module App
|
273
277
|
class Environment
|
274
278
|
attr_reader :storage
|
275
|
-
attr_reader :dispatcher
|
276
279
|
attr_reader :logger
|
277
280
|
|
278
|
-
def initialize(storage,
|
279
|
-
@storage
|
280
|
-
@dispatcher = dispatcher
|
281
|
-
@logger = logger
|
281
|
+
def initialize(storage, logger)
|
282
|
+
@storage, @logger = storage, logger
|
282
283
|
end
|
283
284
|
end
|
284
285
|
|
@@ -309,12 +310,12 @@ module App
|
|
309
310
|
end
|
310
311
|
end
|
311
312
|
|
313
|
+
storage = App::Storage.new # some storage abstraction
|
314
|
+
env = App::Environment.new(storage, Logger.new($stdout))
|
315
|
+
|
312
316
|
dispatcher = Substation::Dispatcher.coerce({
|
313
317
|
'some_use_case' => { 'action' => 'App::SomeUseCase' }
|
314
|
-
})
|
315
|
-
|
316
|
-
storage = App::Storage.new # some storage abstraction
|
317
|
-
env = App::Environment.new(storage, dispatcher, Logger.new($stdout))
|
318
|
+
}, env)
|
318
319
|
|
319
320
|
# :some_input is no person, db.save_person will fail
|
320
321
|
response = dispatcher.call(:some_use_case, :some_input, env)
|
data/config/flay.yml
CHANGED
@@ -60,11 +60,37 @@ module Substation
|
|
60
60
|
|
61
61
|
# Coerce the given +config+ to a {Dispatcher} instance
|
62
62
|
#
|
63
|
+
# @example setup code for all the other examples
|
64
|
+
#
|
65
|
+
# module App
|
66
|
+
# class Environment
|
67
|
+
# def initialize(storage, logger)
|
68
|
+
# @storage, @logger = storage, logger
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# class SomeUseCase
|
73
|
+
# def self.call(request)
|
74
|
+
# data = perform_work
|
75
|
+
# request.success(data)
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# class SomeObserver
|
80
|
+
# def self.call(response)
|
81
|
+
# # do something
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# storage = SomeStorageAbstraction.new
|
87
|
+
# env = App::Environment.new(storage, Logger.new($stdout))
|
88
|
+
#
|
63
89
|
# @example without observers
|
64
90
|
#
|
65
91
|
# dispatcher = Substation::Dispatcher.coerce({
|
66
92
|
# 'some_use_case' => { 'action' => 'SomeUseCase' }
|
67
|
-
# })
|
93
|
+
# }, env)
|
68
94
|
#
|
69
95
|
# @example with a single observer
|
70
96
|
#
|
@@ -73,7 +99,7 @@ module Substation
|
|
73
99
|
# 'action' => 'SomeUseCase',
|
74
100
|
# 'observer' => 'SomeObserver'
|
75
101
|
# }
|
76
|
-
# })
|
102
|
+
# }, env)
|
77
103
|
#
|
78
104
|
# @example with multiple observers
|
79
105
|
#
|
@@ -85,31 +111,16 @@ module Substation
|
|
85
111
|
# 'AnotherObserver'
|
86
112
|
# ]
|
87
113
|
# }
|
88
|
-
# })
|
114
|
+
# }, env)
|
89
115
|
#
|
90
116
|
# @example with Symbol keys and const handlers
|
91
117
|
#
|
92
|
-
# module App
|
93
|
-
# class SomeUseCase
|
94
|
-
# def self.call(request)
|
95
|
-
# data = perform_work
|
96
|
-
# request.success(data)
|
97
|
-
# end
|
98
|
-
# end
|
99
|
-
#
|
100
|
-
# class SomeObserver
|
101
|
-
# def self.call(response)
|
102
|
-
# # do something
|
103
|
-
# end
|
104
|
-
# end
|
105
|
-
# end
|
106
|
-
#
|
107
118
|
# dispatcher = Substation::Dispatcher.coerce({
|
108
119
|
# :some_use_case => {
|
109
120
|
# :action => App::SomeUseCase,
|
110
121
|
# :observer => App::SomeObserver
|
111
122
|
# }
|
112
|
-
# })
|
123
|
+
# }, env)
|
113
124
|
#
|
114
125
|
# @example with Symbol keys and proc handlers
|
115
126
|
#
|
@@ -118,11 +129,14 @@ module Substation
|
|
118
129
|
# :action => Proc.new { |request| request.success(:foo) },
|
119
130
|
# :observer => Proc.new { |response| do_something }
|
120
131
|
# }
|
121
|
-
# })
|
132
|
+
# }, env)
|
122
133
|
#
|
123
134
|
# @param [Hash<#to_sym, Object>] config
|
124
135
|
# the action configuration
|
125
136
|
#
|
137
|
+
# @param [Object] env
|
138
|
+
# the application environment
|
139
|
+
#
|
126
140
|
# @return [Dispatcher]
|
127
141
|
# the coerced instance
|
128
142
|
#
|
@@ -133,8 +147,8 @@ module Substation
|
|
133
147
|
# if action or observer handlers are not coercible
|
134
148
|
#
|
135
149
|
# @api public
|
136
|
-
def self.coerce(config)
|
137
|
-
new(normalize_config(config))
|
150
|
+
def self.coerce(config, env)
|
151
|
+
new(normalize_config(config), env)
|
138
152
|
end
|
139
153
|
|
140
154
|
# Normalize the given +config+
|
@@ -160,7 +174,7 @@ module Substation
|
|
160
174
|
|
161
175
|
private_class_method :normalize_config
|
162
176
|
|
163
|
-
include Concord.new(:actions)
|
177
|
+
include Concord.new(:actions, :env)
|
164
178
|
include Adamantium
|
165
179
|
|
166
180
|
# Invoke the action identified by +name+
|
@@ -169,8 +183,8 @@ module Substation
|
|
169
183
|
#
|
170
184
|
# module App
|
171
185
|
# class Environment
|
172
|
-
# def initialize(
|
173
|
-
# @
|
186
|
+
# def initialize(storage, logger)
|
187
|
+
# @storage, @logger = storage, logger
|
174
188
|
# end
|
175
189
|
# end
|
176
190
|
#
|
@@ -182,11 +196,10 @@ module Substation
|
|
182
196
|
# end
|
183
197
|
# end
|
184
198
|
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
# }
|
188
|
-
#
|
189
|
-
# env = App::Environment.new(dispatcher, Logger.new($stdout))
|
199
|
+
# storage = SomeStorageAbstraction.new
|
200
|
+
# env = App::Environment.new(storage, Logger.new($stdout))
|
201
|
+
# config = { :some_use_case => { :action => App::SomeUseCase } }
|
202
|
+
# dispatcher = Substation::Dispatcher.coerce(config, env)
|
190
203
|
#
|
191
204
|
# response = dispatcher.call(:some_use_case, :some_input, env)
|
192
205
|
# response.success? # => true
|
@@ -197,9 +210,6 @@ module Substation
|
|
197
210
|
# @param [Object] input
|
198
211
|
# the input model instance to pass to the action
|
199
212
|
#
|
200
|
-
# @param [Object] env
|
201
|
-
# the application environment
|
202
|
-
#
|
203
213
|
# @return [Response]
|
204
214
|
# the response returned when calling the action
|
205
215
|
#
|
@@ -207,7 +217,7 @@ module Substation
|
|
207
217
|
# if no action is registered for +name+
|
208
218
|
#
|
209
219
|
# @api public
|
210
|
-
def call(name, input
|
220
|
+
def call(name, input)
|
211
221
|
fetch(name).call(Request.new(env, input))
|
212
222
|
end
|
213
223
|
|
@@ -216,6 +226,12 @@ module Substation
|
|
216
226
|
# @example
|
217
227
|
#
|
218
228
|
# module App
|
229
|
+
# class Environment
|
230
|
+
# def initialize(storage, logger)
|
231
|
+
# @storage, @logger = storage, logger
|
232
|
+
# end
|
233
|
+
# end
|
234
|
+
#
|
219
235
|
# class SomeUseCase
|
220
236
|
# def self.call(request)
|
221
237
|
# data = perform_work
|
@@ -224,9 +240,10 @@ module Substation
|
|
224
240
|
# end
|
225
241
|
# end
|
226
242
|
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
# }
|
243
|
+
# storage = SomeStorageAbstraction.new
|
244
|
+
# env = App::Environment.new(storage, Logger.new($stdout))
|
245
|
+
# config = { :some_use_case => { :action => App::SomeUseCase } }
|
246
|
+
# dispatcher = Substation::Dispatcher.coerce(config, env)
|
230
247
|
#
|
231
248
|
# dispatcher.action_names # => #<Set: {:some_use_case}>
|
232
249
|
#
|
data/lib/substation/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'substation'
|
2
1
|
require 'devtools/spec_helper'
|
3
2
|
|
4
3
|
module Spec
|
@@ -28,6 +27,25 @@ module Spec
|
|
28
27
|
|
29
28
|
end
|
30
29
|
|
30
|
+
if ENV['COVERAGE'] == 'true'
|
31
|
+
require 'simplecov'
|
32
|
+
require 'coveralls'
|
33
|
+
|
34
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
35
|
+
SimpleCov::Formatter::HTMLFormatter,
|
36
|
+
Coveralls::SimpleCov::Formatter
|
37
|
+
]
|
38
|
+
|
39
|
+
SimpleCov.start do
|
40
|
+
command_name 'spec:unit'
|
41
|
+
add_filter 'config'
|
42
|
+
add_filter 'spec'
|
43
|
+
minimum_coverage 100
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
require 'substation'
|
48
|
+
|
31
49
|
include Substation
|
32
50
|
|
33
51
|
RSpec.configure do |config|
|
@@ -6,8 +6,9 @@ describe Dispatcher, '#action_names' do
|
|
6
6
|
|
7
7
|
subject { object.action_names }
|
8
8
|
|
9
|
-
let(:object) { described_class.new(config) }
|
9
|
+
let(:object) { described_class.new(config, env) }
|
10
10
|
let(:config) { { :test => { :action => Spec::Action::Success } } }
|
11
|
+
let(:env) { mock }
|
11
12
|
|
12
13
|
it { should eql(Set[ :test ]) }
|
13
14
|
end
|
@@ -4,9 +4,9 @@ require 'spec_helper'
|
|
4
4
|
|
5
5
|
describe Dispatcher, '#call' do
|
6
6
|
|
7
|
-
subject { object.call(action_name, input
|
7
|
+
subject { object.call(action_name, input) }
|
8
8
|
|
9
|
-
let(:object) { described_class.coerce(config)
|
9
|
+
let(:object) { described_class.coerce(config, env) }
|
10
10
|
let(:config) { { 'test' => { 'action' => 'Spec::Action::Success' } } }
|
11
11
|
let(:request) { Request.new(env, input) }
|
12
12
|
let(:input) { mock }
|
@@ -4,15 +4,17 @@ require 'spec_helper'
|
|
4
4
|
|
5
5
|
describe Dispatcher, '.coerce' do
|
6
6
|
|
7
|
-
subject { described_class.coerce(config) }
|
7
|
+
subject { described_class.coerce(config, env) }
|
8
8
|
|
9
9
|
let(:config) {{
|
10
10
|
'test' => { 'action' => 'Spec::Action::Success' }
|
11
11
|
}}
|
12
12
|
|
13
|
+
let(:env) { mock }
|
14
|
+
|
13
15
|
let(:coerced) {{
|
14
16
|
:test => described_class::Action.coerce(:action => 'Spec::Action::Success')
|
15
17
|
}}
|
16
18
|
|
17
|
-
it { should eql(described_class.new(coerced)) }
|
19
|
+
it { should eql(described_class.new(coerced, env)) }
|
18
20
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: substation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: adamantium
|
@@ -105,6 +105,7 @@ files:
|
|
105
105
|
- .rspec
|
106
106
|
- .rvmrc
|
107
107
|
- .travis.yml
|
108
|
+
- Changelog.md
|
108
109
|
- Gemfile
|
109
110
|
- Gemfile.devtools
|
110
111
|
- Guardfile
|