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 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. Clients can use
33
- `Substation::Dispatcher#call(name, input, env)` to dispatch to any
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 and the dispatcher itself.
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, dispatcher, logger)
279
- @storage = 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
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 6
3
- total_score: 51
3
+ total_score: 46
@@ -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(dispatcher, logger)
173
- # @dispatcher, @logger = dispatcher, logger
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
- # dispatcher = Substation::Dispatcher.coerce({
186
- # :some_use_case => { :action => App::SomeUseCase }
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, env)
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
- # dispatcher = Substation::Dispatcher.coerce({
228
- # :some_use_case => { :action => App::SomeUseCase }
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
  #
@@ -1,4 +1,4 @@
1
1
  module Substation
2
2
  # Gem version
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.0.2'.freeze
4
4
  end
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, env) }
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.1
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-14 00:00:00.000000000 Z
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