substation 0.0.5 → 0.0.6
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 +6 -1
- data/README.md +166 -29
- data/config/flay.yml +1 -1
- data/lib/substation/support/utils.rb +1 -1
- data/lib/substation/version.rb +1 -1
- data/spec/integration/substation/dispatcher/call_spec.rb +234 -0
- data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +6 -0
- metadata +3 -2
data/Changelog.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# v0.0.6 2013-05-17
|
|
2
|
+
|
|
3
|
+
* [fixed] Fixed bug for actions configured with a const handler (snusnu)
|
|
4
|
+
|
|
5
|
+
[Compare v0.0.5..v0.0.6](https://github.com/snusnu/substation/compare/v0.0.5...v0.0.6)
|
|
6
|
+
|
|
1
7
|
# v0.0.5 2013-05-17
|
|
2
8
|
|
|
3
9
|
* [feature] Shorter action config when no observers are needed (snusnu)
|
|
@@ -11,7 +17,6 @@ dispatcher = Substation::Dispatcher.coerce({
|
|
|
11
17
|
:some_use_case => Proc.new { |request| request.success(:data) }
|
|
12
18
|
}, env)
|
|
13
19
|
```
|
|
14
|
-
|
|
15
20
|
[Compare v0.0.4..v0.0.5](https://github.com/snusnu/substation/compare/v0.0.4...v0.0.5)
|
|
16
21
|
|
|
17
22
|
# v0.0.4 2013-05-15
|
data/README.md
CHANGED
|
@@ -282,58 +282,195 @@ the course of performing a usecase (like a logger or a storage engine
|
|
|
282
282
|
abstraction), you can encapsulate these objects within an application
|
|
283
283
|
specific environment object, and send that along to every action.
|
|
284
284
|
|
|
285
|
-
Here's a
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
The example builds on top of the application specific action baseclass
|
|
289
|
-
shown above:
|
|
285
|
+
Here's a complete example with an environment that encapsulates a very
|
|
286
|
+
primitive storage abstraction object, one simple business entity, and a
|
|
287
|
+
few simple actions.
|
|
290
288
|
|
|
291
289
|
```ruby
|
|
292
290
|
module App
|
|
291
|
+
|
|
292
|
+
class Database
|
|
293
|
+
include Concord.new(:entries)
|
|
294
|
+
|
|
295
|
+
def [](relation_name)
|
|
296
|
+
Relation.new(entries[relation_name])
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
class Relation
|
|
300
|
+
include Concord.new(:tuples)
|
|
301
|
+
include Enumerable
|
|
302
|
+
|
|
303
|
+
def each(&block)
|
|
304
|
+
return to_enum unless block_given?
|
|
305
|
+
tuples.each(&block)
|
|
306
|
+
self
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def all
|
|
310
|
+
tuples
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def insert(tuple)
|
|
314
|
+
self.class.new(tuples + [tuple])
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
module Models
|
|
320
|
+
|
|
321
|
+
class Person
|
|
322
|
+
include Concord.new(:attributes)
|
|
323
|
+
|
|
324
|
+
def id
|
|
325
|
+
attributes[:id]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def name
|
|
329
|
+
attributes[:name]
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end # module Models
|
|
333
|
+
|
|
293
334
|
class Environment
|
|
335
|
+
include Concord.new(:storage)
|
|
336
|
+
include Adamantium::Flat
|
|
337
|
+
|
|
294
338
|
attr_reader :storage
|
|
295
|
-
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
class Storage
|
|
342
|
+
include Concord.new(:db)
|
|
343
|
+
include Adamantium::Flat
|
|
344
|
+
|
|
345
|
+
include Models
|
|
346
|
+
|
|
347
|
+
def list_people
|
|
348
|
+
db[:people].all.map { |tuple| Person.new(tuple) }
|
|
349
|
+
end
|
|
296
350
|
|
|
297
|
-
def
|
|
298
|
-
|
|
351
|
+
def load_person(id)
|
|
352
|
+
Person.new(db[:people].select { |tuple| tuple[:id] == id }.first)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def create_person(person)
|
|
356
|
+
relation = db[:people].insert(:id => person.id, :name => person.name)
|
|
357
|
+
relation.map { |tuple| Person.new(tuple) }
|
|
299
358
|
end
|
|
300
359
|
end
|
|
301
360
|
|
|
361
|
+
class App
|
|
362
|
+
include Concord.new(:dispatcher)
|
|
363
|
+
include Adamantium::Flat
|
|
364
|
+
|
|
365
|
+
def call(name, input = nil)
|
|
366
|
+
dispatcher.call(name, input)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Base class for all actions
|
|
371
|
+
#
|
|
372
|
+
# @abstract
|
|
302
373
|
class Action
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
374
|
+
|
|
375
|
+
include AbstractType
|
|
376
|
+
include Adamantium::Flat
|
|
377
|
+
|
|
378
|
+
def self.call(request)
|
|
379
|
+
new(request).call
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def initialize(request)
|
|
383
|
+
@request = request
|
|
384
|
+
@env = @request.env
|
|
385
|
+
@input = @request.input
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
abstract_method :call
|
|
389
|
+
|
|
390
|
+
private
|
|
391
|
+
|
|
392
|
+
attr_reader :request
|
|
393
|
+
attr_reader :env
|
|
394
|
+
attr_reader :input
|
|
306
395
|
|
|
307
396
|
def db
|
|
308
397
|
@env.storage
|
|
309
398
|
end
|
|
399
|
+
|
|
400
|
+
def success(data)
|
|
401
|
+
@request.success(data)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def error(data)
|
|
405
|
+
@request.error(data)
|
|
406
|
+
end
|
|
310
407
|
end
|
|
311
408
|
|
|
312
|
-
|
|
409
|
+
module Actions
|
|
410
|
+
class ListPeople < Action
|
|
313
411
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
412
|
+
def call
|
|
413
|
+
success(db.list_people)
|
|
414
|
+
end
|
|
317
415
|
end
|
|
318
416
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
417
|
+
class LoadPerson < Action
|
|
418
|
+
def initialize(request)
|
|
419
|
+
super
|
|
420
|
+
@id = input
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def call
|
|
424
|
+
success(db.load_person(@id))
|
|
324
425
|
end
|
|
325
426
|
end
|
|
427
|
+
|
|
428
|
+
class CreatePerson < Action
|
|
429
|
+
|
|
430
|
+
def initialize(request)
|
|
431
|
+
super
|
|
432
|
+
@person = input
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def call
|
|
436
|
+
success(db.create_person(@person))
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
end # module Actions
|
|
441
|
+
|
|
442
|
+
module Observers
|
|
443
|
+
LogEvent = Class.new { def self.call(response); end }
|
|
444
|
+
SendEmail = Class.new { def self.call(response); end }
|
|
326
445
|
end
|
|
327
|
-
end
|
|
328
446
|
|
|
329
|
-
|
|
330
|
-
|
|
447
|
+
DB = Database.new({
|
|
448
|
+
:people => [{
|
|
449
|
+
:id => 1,
|
|
450
|
+
:name => 'John'
|
|
451
|
+
}]
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
actions = {
|
|
455
|
+
:list_people => Actions::ListPeople,
|
|
456
|
+
:load_person => Actions::LoadPerson,
|
|
457
|
+
:create_person => {
|
|
458
|
+
:action => Actions::CreatePerson,
|
|
459
|
+
:observer => [
|
|
460
|
+
Observers::LogEvent,
|
|
461
|
+
Observers::SendEmail
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
storage = Storage.new(DB)
|
|
467
|
+
env = Environment.new(storage)
|
|
468
|
+
dispatcher = Substation::Dispatcher.coerce(actions, env)
|
|
331
469
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}, env)
|
|
470
|
+
APP = App.new(dispatcher)
|
|
471
|
+
end
|
|
335
472
|
|
|
336
|
-
|
|
337
|
-
response
|
|
338
|
-
response.
|
|
473
|
+
response = App::APP.call(:list_companies)
|
|
474
|
+
response.success? # => true
|
|
475
|
+
response.output # => [#<App::Models::Person attributes={:id=>1, :name=>"John"}>]
|
|
339
476
|
```
|
data/config/flay.yml
CHANGED
data/lib/substation/version.rb
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module App
|
|
6
|
+
|
|
7
|
+
class Database
|
|
8
|
+
include Concord.new(:entries)
|
|
9
|
+
|
|
10
|
+
def [](relation_name)
|
|
11
|
+
Relation.new(entries[relation_name])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Relation
|
|
15
|
+
include Concord.new(:tuples)
|
|
16
|
+
include Enumerable
|
|
17
|
+
|
|
18
|
+
def each(&block)
|
|
19
|
+
return to_enum unless block_given?
|
|
20
|
+
tuples.each(&block)
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def all
|
|
25
|
+
tuples
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def insert(tuple)
|
|
29
|
+
self.class.new(tuples + [tuple])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module Models
|
|
35
|
+
|
|
36
|
+
class Person
|
|
37
|
+
include Concord.new(:attributes)
|
|
38
|
+
|
|
39
|
+
def id
|
|
40
|
+
attributes[:id]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def name
|
|
44
|
+
attributes[:name]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end # module Models
|
|
48
|
+
|
|
49
|
+
class Environment
|
|
50
|
+
include Concord.new(:storage)
|
|
51
|
+
include Adamantium::Flat
|
|
52
|
+
|
|
53
|
+
attr_reader :storage
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class Storage
|
|
57
|
+
include Concord.new(:db)
|
|
58
|
+
include Adamantium::Flat
|
|
59
|
+
|
|
60
|
+
include Models
|
|
61
|
+
|
|
62
|
+
def list_people
|
|
63
|
+
db[:people].all.map { |tuple| Person.new(tuple) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def load_person(id)
|
|
67
|
+
Person.new(db[:people].select { |tuple| tuple[:id] == id }.first)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def create_person(person)
|
|
71
|
+
relation = db[:people].insert(:id => person.id, :name => person.name)
|
|
72
|
+
relation.map { |tuple| Person.new(tuple) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class App
|
|
77
|
+
include Concord.new(:dispatcher)
|
|
78
|
+
include Adamantium::Flat
|
|
79
|
+
|
|
80
|
+
def call(name, input = nil)
|
|
81
|
+
dispatcher.call(name, input)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Base class for all actions
|
|
86
|
+
#
|
|
87
|
+
# @abstract
|
|
88
|
+
class Action
|
|
89
|
+
|
|
90
|
+
include AbstractType
|
|
91
|
+
include Adamantium::Flat
|
|
92
|
+
|
|
93
|
+
def self.call(request)
|
|
94
|
+
new(request).call
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def initialize(request)
|
|
98
|
+
@request = request
|
|
99
|
+
@env = @request.env
|
|
100
|
+
@input = @request.input
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
abstract_method :call
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
attr_reader :request
|
|
108
|
+
attr_reader :env
|
|
109
|
+
attr_reader :input
|
|
110
|
+
|
|
111
|
+
def db
|
|
112
|
+
@env.storage
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def success(data)
|
|
116
|
+
@request.success(data)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def error(data)
|
|
120
|
+
@request.error(data)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
module Actions
|
|
125
|
+
class ListPeople < Action
|
|
126
|
+
|
|
127
|
+
def call
|
|
128
|
+
success(db.list_people)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class LoadPerson < Action
|
|
133
|
+
def initialize(request)
|
|
134
|
+
super
|
|
135
|
+
@id = input
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def call
|
|
139
|
+
success(db.load_person(@id))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class CreatePerson < Action
|
|
144
|
+
|
|
145
|
+
def initialize(request)
|
|
146
|
+
super
|
|
147
|
+
@person = input
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def call
|
|
151
|
+
success(db.create_person(@person))
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
end # module Actions
|
|
156
|
+
|
|
157
|
+
module Observers
|
|
158
|
+
LogEvent = Class.new { def self.call(response); end }
|
|
159
|
+
SendEmail = Class.new { def self.call(response); end }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
DB = Database.new({
|
|
163
|
+
:people => [{
|
|
164
|
+
:id => 1,
|
|
165
|
+
:name => 'John'
|
|
166
|
+
}]
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
actions = {
|
|
170
|
+
:list_people => Actions::ListPeople,
|
|
171
|
+
:load_person => Actions::LoadPerson,
|
|
172
|
+
:create_person => {
|
|
173
|
+
:action => Actions::CreatePerson,
|
|
174
|
+
:observer => [
|
|
175
|
+
Observers::LogEvent,
|
|
176
|
+
Observers::SendEmail
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
storage = Storage.new(DB)
|
|
182
|
+
env = Environment.new(storage)
|
|
183
|
+
dispatcher = Substation::Dispatcher.coerce(actions, env)
|
|
184
|
+
|
|
185
|
+
APP = App.new(dispatcher)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe App::APP, '#call' do
|
|
189
|
+
|
|
190
|
+
context "when dispatching an action" do
|
|
191
|
+
subject { object.call(action, input) }
|
|
192
|
+
|
|
193
|
+
let(:object) { described_class }
|
|
194
|
+
let(:request) { Substation::Request.new(env, input) }
|
|
195
|
+
let(:env) { App::Environment.new(storage) }
|
|
196
|
+
let(:storage) { App::Storage.new(App::DB) }
|
|
197
|
+
let(:response) { Substation::Response::Success.new(request, output) }
|
|
198
|
+
|
|
199
|
+
let(:john) { App::Models::Person.new(:id => 1, :name => 'John') }
|
|
200
|
+
let(:jane) { App::Models::Person.new(:id => 2, :name => 'Jane') }
|
|
201
|
+
|
|
202
|
+
context "with no input data" do
|
|
203
|
+
let(:action) { :list_people }
|
|
204
|
+
let(:input) { nil }
|
|
205
|
+
let(:output) { [ john ] }
|
|
206
|
+
|
|
207
|
+
it { should eql(response) }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context "with input data" do
|
|
211
|
+
context "and no observer" do
|
|
212
|
+
let(:action) { :load_person }
|
|
213
|
+
let(:input) { 1 }
|
|
214
|
+
let(:output) { john }
|
|
215
|
+
|
|
216
|
+
it { should eq(response) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
context "and observers" do
|
|
220
|
+
let(:action) { :create_person }
|
|
221
|
+
let(:input) { jane }
|
|
222
|
+
let(:output) { [ john, jane ] }
|
|
223
|
+
|
|
224
|
+
before do
|
|
225
|
+
App::Observers::LogEvent.should_receive(:call).with(response).ordered
|
|
226
|
+
App::Observers::SendEmail.should_receive(:call).with(response).ordered
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it { should eql(response) }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -18,6 +18,12 @@ describe Utils, '.coerce_callable' do
|
|
|
18
18
|
it { should be(Spec::Action::Success) }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
context "with a const handler" do
|
|
22
|
+
let(:handler) { Spec::Action::Success }
|
|
23
|
+
|
|
24
|
+
it { should be(Spec::Action::Success) }
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
context "with a Proc handler" do
|
|
22
28
|
let(:handler) { Proc.new { |response| respone } }
|
|
23
29
|
|
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.6
|
|
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-17 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: adamantium
|
|
@@ -126,6 +126,7 @@ files:
|
|
|
126
126
|
- lib/substation/response.rb
|
|
127
127
|
- lib/substation/support/utils.rb
|
|
128
128
|
- lib/substation/version.rb
|
|
129
|
+
- spec/integration/substation/dispatcher/call_spec.rb
|
|
129
130
|
- spec/spec_helper.rb
|
|
130
131
|
- spec/unit/substation/dispatcher/action/call_spec.rb
|
|
131
132
|
- spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb
|