substation 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
+
[![Coverage Status](https://coveralls.io/repos/snusnu/substation/badge.png?branch=master)][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
|