light-service 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/README.md +126 -21
- data/RELEASES.md +3 -0
- data/gemfiles/activesupport_3.gemfile +1 -1
- data/gemfiles/activesupport_4.gemfile +1 -1
- data/gemfiles/activesupport_5.gemfile +1 -1
- data/lib/light-service/action.rb +22 -2
- data/lib/light-service/organizer.rb +27 -0
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +2 -2
- data/spec/acceptance/add_numbers_spec.rb +3 -3
- data/spec/acceptance/after_actions_spec.rb +67 -0
- data/spec/acceptance/before_actions_spec.rb +109 -0
- data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +6 -6
- data/spec/acceptance/orchestrator/execute_spec.rb +2 -2
- data/spec/acceptance/orchestrator/iterate_spec.rb +2 -2
- data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +2 -2
- data/spec/acceptance/orchestrator/reduce_if_spec.rb +2 -2
- data/spec/acceptance/orchestrator/reduce_until_spec.rb +1 -1
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +4 -4
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +6 -6
- data/spec/acceptance/organizer/execute_spec.rb +2 -2
- data/spec/acceptance/organizer/iterate_spec.rb +6 -20
- data/spec/acceptance/organizer/reduce_if_spec.rb +2 -2
- data/spec/acceptance/organizer/reduce_until_spec.rb +1 -1
- data/spec/acceptance/organizer/with_callback_spec.rb +7 -66
- data/spec/sample/calculates_tax_spec.rb +1 -4
- data/spec/test_doubles.rb +77 -14
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24c5a758ab50b3cd0ec404ef0b8a426a3e77e42b
|
4
|
+
data.tar.gz: 03ec6d27e900c9685bd141340c2b191df781ab84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef89eee65b07381b629c9f4b8e5aad0e4e719e370e8aa84c32a23f65824d42c3e9c14424810b8518a244df4769b3f2e4f22a1752f6c498e44e146b803041bff3
|
7
|
+
data.tar.gz: 3476522df0af4775eff555a986d0dffbfed8dad3c0cd77a7a5eb19ca25b39c7109c8da8756a30625d39bf89e10fb7a54fd6212c15955bc544c37e66f2be6293d
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -11,7 +11,25 @@
|
|
11
11
|
![Orchestrators-Deprecated](resources/orchestrators_deprecated.svg)
|
12
12
|
<br>Version 0.9.0 deprecates Orchestrators and moves all their functionalities into Organizers. Please check out [this PR](https://github.com/adomokos/light-service/pull/132) to see the changes.
|
13
13
|
|
14
|
-
<br
|
14
|
+
<br>
|
15
|
+
|
16
|
+
## Table of Content
|
17
|
+
* [Why LightService?](#why-lightservice)
|
18
|
+
* [Stopping the Series of Actions](#stopping-the-series-of-actions)
|
19
|
+
* [Failing the Context](#failing-the-context)
|
20
|
+
* [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
|
21
|
+
* [Benchmarking Actions with Around Advice](#benchmarking-actions-with-around-advice)
|
22
|
+
* [Before and After Action Hooks](#before-and-after-action-hooks)
|
23
|
+
* [Key Aliases](#key-aliases)
|
24
|
+
* [Logging](#logging)
|
25
|
+
* [Error Codes](#error-codes)
|
26
|
+
* [Action Rollback](#action-rollback)
|
27
|
+
* [Localizing Messages](#localizing-messages)
|
28
|
+
* [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
|
29
|
+
* [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
|
30
|
+
|
31
|
+
|
32
|
+
## Why LightService?
|
15
33
|
|
16
34
|
What do you think of this code?
|
17
35
|
|
@@ -116,8 +134,10 @@ class CalculatesOrderTaxAction
|
|
116
134
|
extend ::LightService::Action
|
117
135
|
expects :order, :tax_percentage
|
118
136
|
|
119
|
-
|
120
|
-
|
137
|
+
# I am using ctx as an abbreviation for context
|
138
|
+
executed do |ctx|
|
139
|
+
order = ctx.order
|
140
|
+
order.tax = (order.total * (ctx.tax_percentage/100)).round(2)
|
121
141
|
end
|
122
142
|
|
123
143
|
end
|
@@ -126,9 +146,9 @@ class ProvidesFreeShippingAction
|
|
126
146
|
extend LightService::Action
|
127
147
|
expects :order
|
128
148
|
|
129
|
-
executed do |
|
130
|
-
if order.total_with_tax > 200
|
131
|
-
order.provide_free_shipping!
|
149
|
+
executed do |ctx|
|
150
|
+
if ctx.order.total_with_tax > 200
|
151
|
+
ctx.order.provide_free_shipping!
|
132
152
|
end
|
133
153
|
end
|
134
154
|
end
|
@@ -156,18 +176,6 @@ I gave a [talk at RailsConf 2013](http://www.adomokos.com/2013/06/simple-and-ele
|
|
156
176
|
simple and elegant Rails code where I told the story of how LightService was extracted from the projects I had worked on.
|
157
177
|
|
158
178
|
|
159
|
-
## Table of Content
|
160
|
-
* [Stopping the Series of Actions](#stopping-the-series-of-actions)
|
161
|
-
* [Failing the Context](#failing-the-context)
|
162
|
-
* [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
|
163
|
-
* [Benchmarking Actions with Around Advice](#benchmarking-actions-with-around-advice)
|
164
|
-
* [Key Aliases](#key-aliases)
|
165
|
-
* [Logging](#logging)
|
166
|
-
* [Error Codes](#error-codes)
|
167
|
-
* [Action Rollback](#action-rollback)
|
168
|
-
* [Localizing Messages](#localizing-messages)
|
169
|
-
* [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
|
170
|
-
* [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
|
171
179
|
|
172
180
|
## Stopping the Series of Actions
|
173
181
|
When nothing unexpected happens during the organizer's call, the returned `context` will be successful. Here is how you can check for this:
|
@@ -196,7 +204,7 @@ When something goes wrong in an action and you want to halt the chain, you need
|
|
196
204
|
The context's `fail!` method can take an optional message argument, this message might help describing what went wrong.
|
197
205
|
In case you need to return immediately from the point of failure, you have to do that by calling `next context`.
|
198
206
|
|
199
|
-
In case you want to fail the context and stop the execution of the executed block, use the `fail_and_return!('something went
|
207
|
+
In case you want to fail the context and stop the execution of the executed block, use the `fail_and_return!('something went wrong')` method.
|
200
208
|
This will immediately leave the block, you don't need to call `next context` to return from the block.
|
201
209
|
|
202
210
|
Here is an example:
|
@@ -277,6 +285,103 @@ end
|
|
277
285
|
|
278
286
|
Any object passed into `around_each` must respond to #call with two arguments: the action name and the context it will execute with. It is also passed a block, where LightService's action execution will be done in, so the result must be returned. While this is a little work, it also gives you before and after state access to the data for any auditing and/or checks you may need to accomplish.
|
279
287
|
|
288
|
+
## Before and After Action Hooks
|
289
|
+
|
290
|
+
In case you need to inject code right before and after the actions are executed, you can use the `before_actions` and `after_actions` hooks. It accepts one or multiple lambdas that the Action implementation will invoke. This addition to LightService is a great way to decouple instrumentation from business logic.
|
291
|
+
|
292
|
+
Consider this code:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
class SomeOrganizer
|
296
|
+
extend LightService::Organizer
|
297
|
+
|
298
|
+
def call(ctx)
|
299
|
+
with(ctx).reduce(actions)
|
300
|
+
end
|
301
|
+
|
302
|
+
def actions
|
303
|
+
OneAction,
|
304
|
+
TwoAction,
|
305
|
+
ThreeAction
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
class TwoAction
|
310
|
+
extend LightService::Action
|
311
|
+
expects :user, :logger
|
312
|
+
|
313
|
+
executed do |ctx|
|
314
|
+
# Logging information
|
315
|
+
if ctx.user.role == 'admin'
|
316
|
+
ctx.logger.info('admin is doing something')
|
317
|
+
end
|
318
|
+
|
319
|
+
ctx.user.do_something
|
320
|
+
end
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
The logging logic makes `TwoAction` more complex, there is more code for logging than for business logic.
|
325
|
+
|
326
|
+
You have two options to decouple instrumentation from real logic with `before_actions` and `after_actions` hooks:
|
327
|
+
|
328
|
+
1. Declare your hooks in the Organizer
|
329
|
+
2. Attach hooks to the Organizer from the outside
|
330
|
+
|
331
|
+
This is how you can declaratively add before and after hooks to the Organizer:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
class SomeOrganizer
|
335
|
+
extend LightService::Organizer
|
336
|
+
before_actions (lambda do |ctx|
|
337
|
+
if ctx.current_action == TwoAction
|
338
|
+
return unless ctx.user.role == 'admin'
|
339
|
+
ctx.logger.info('admin is doing something')
|
340
|
+
end
|
341
|
+
end)
|
342
|
+
after_actions (lambda do |ctx|
|
343
|
+
if ctx.current_action == TwoAction
|
344
|
+
return unless ctx.user.role == 'admin'
|
345
|
+
ctx.logger.info('admin is DONE doing something')
|
346
|
+
end
|
347
|
+
end)
|
348
|
+
|
349
|
+
def call(ctx)
|
350
|
+
with(ctx).reduce(actions)
|
351
|
+
end
|
352
|
+
|
353
|
+
def actions
|
354
|
+
OneAction,
|
355
|
+
TwoAction,
|
356
|
+
ThreeAction
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
class TwoAction
|
361
|
+
extend LightService::Action
|
362
|
+
expects :user
|
363
|
+
|
364
|
+
executed do |ctx|
|
365
|
+
ctx.user.do_something
|
366
|
+
end
|
367
|
+
end
|
368
|
+
```
|
369
|
+
|
370
|
+
Note how the action has no logging logic after this change. Also, you can target before and after action logic for specific actions, as the `ctx.current_action` will have the class name of the currently processed action. In the example above, logging will occur only for `TwoAction` and not for `OneAction` or `ThreeAction`.
|
371
|
+
|
372
|
+
Here is how you can declaratively add `before_hooks` or `after_hooks` to your Organizer from the outside:
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
SomeOrganizer.before_actions =
|
376
|
+
lambda do |ctx|
|
377
|
+
if ctx.current_action == TwoAction
|
378
|
+
return unless ctx.user.role == 'admin'
|
379
|
+
ctx.logger.info('admin is doing something')
|
380
|
+
end
|
381
|
+
end
|
382
|
+
```
|
383
|
+
|
384
|
+
These ideas are originally from Aspect Oriented Programming, read more about them [here](https://en.wikipedia.org/wiki/Aspect-oriented_programming).
|
280
385
|
|
281
386
|
## Expects and Promises
|
282
387
|
The `expects` and `promises` macros are rules for the inputs/outputs of an action.
|
@@ -619,7 +724,7 @@ class ExtractsTransformsLoadsData
|
|
619
724
|
extend LightService::Organizer
|
620
725
|
|
621
726
|
def self.call(connection)
|
622
|
-
with(:connection => connection).reduce(
|
727
|
+
with(:connection => connection).reduce(actions)
|
623
728
|
end
|
624
729
|
|
625
730
|
def self.actions
|
@@ -629,7 +734,7 @@ class ExtractsTransformsLoadsData
|
|
629
734
|
reduce_if(->(ctx) { ctx.retrieved_items.empty? }, [
|
630
735
|
NotifiesEngineeringTeamAction
|
631
736
|
]),
|
632
|
-
iterate(:
|
737
|
+
iterate(:retrieved_items, [
|
633
738
|
TransformsData
|
634
739
|
]),
|
635
740
|
LoadsData,
|
data/RELEASES.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
A brief list of new features and changes introduced with the specified version.
|
2
2
|
|
3
|
+
### 0.10.0
|
4
|
+
* Adding [before_actions and after_actions hooks](https://github.com/adomokos/light-service/pull/144).
|
5
|
+
|
3
6
|
### 0.9.0
|
4
7
|
* [Deprecate Orchestrator](https://github.com/adomokos/light-service/pull/132) by moving its functionality to Organizers.
|
5
8
|
|
data/lib/light-service/action.rb
CHANGED
@@ -23,11 +23,11 @@ module LightService
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def expected_keys
|
26
|
-
@
|
26
|
+
@expected_keys ||= []
|
27
27
|
end
|
28
28
|
|
29
29
|
def promised_keys
|
30
|
-
@
|
30
|
+
@promised_keys ||= []
|
31
31
|
end
|
32
32
|
|
33
33
|
def executed
|
@@ -42,7 +42,9 @@ module LightService
|
|
42
42
|
action_context.define_accessor_methods_for_keys(all_keys)
|
43
43
|
|
44
44
|
catch(:jump_when_failed) do
|
45
|
+
call_before_action(action_context)
|
45
46
|
yield(action_context)
|
47
|
+
call_after_action(action_context)
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
@@ -70,6 +72,24 @@ module LightService
|
|
70
72
|
def all_keys
|
71
73
|
expected_keys + promised_keys
|
72
74
|
end
|
75
|
+
|
76
|
+
def call_before_action(context)
|
77
|
+
invoke_callbacks(context[:_before_actions], context)
|
78
|
+
end
|
79
|
+
|
80
|
+
def call_after_action(context)
|
81
|
+
invoke_callbacks(context[:_after_actions], context)
|
82
|
+
end
|
83
|
+
|
84
|
+
def invoke_callbacks(callbacks, context)
|
85
|
+
return context unless callbacks
|
86
|
+
|
87
|
+
callbacks.each do |cb|
|
88
|
+
cb.call(context)
|
89
|
+
end
|
90
|
+
|
91
|
+
context
|
92
|
+
end
|
73
93
|
end
|
74
94
|
end
|
75
95
|
end
|
@@ -19,6 +19,17 @@ module LightService
|
|
19
19
|
def with(data = {})
|
20
20
|
VerifyCallMethodExists.run(self, caller(1..1).first)
|
21
21
|
data[:_aliases] = @aliases if @aliases
|
22
|
+
|
23
|
+
if @before_actions
|
24
|
+
data[:_before_actions] = @before_actions.dup
|
25
|
+
@before_actions = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
if @after_actions
|
29
|
+
data[:_after_actions] = @after_actions.dup
|
30
|
+
@after_actions = nil
|
31
|
+
end
|
32
|
+
|
22
33
|
WithReducerFactory.make(self).with(data)
|
23
34
|
end
|
24
35
|
|
@@ -51,6 +62,22 @@ module LightService
|
|
51
62
|
def aliases(key_hash)
|
52
63
|
@aliases = key_hash
|
53
64
|
end
|
65
|
+
|
66
|
+
def before_actions(*logic)
|
67
|
+
self.before_actions = logic
|
68
|
+
end
|
69
|
+
|
70
|
+
def before_actions=(logic)
|
71
|
+
@before_actions = [logic].flatten
|
72
|
+
end
|
73
|
+
|
74
|
+
def after_actions(*logic)
|
75
|
+
self.after_actions = logic
|
76
|
+
end
|
77
|
+
|
78
|
+
def after_actions=(logic)
|
79
|
+
@after_actions = [logic].flatten
|
80
|
+
end
|
54
81
|
end
|
55
82
|
end
|
56
83
|
end
|
data/light-service.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.add_dependency("activesupport", ">= 3.0")
|
20
20
|
|
21
21
|
gem.add_development_dependency("rspec", "~> 3.0")
|
22
|
-
gem.add_development_dependency("simplecov", "~> 0.
|
23
|
-
gem.add_development_dependency("rubocop", "~> 0.
|
22
|
+
gem.add_development_dependency("simplecov", "~> 0.16.1")
|
23
|
+
gem.add_development_dependency("rubocop", "~> 0.53")
|
24
24
|
gem.add_development_dependency("pry", "~> 0.10")
|
25
25
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'test_doubles'
|
3
3
|
|
4
|
-
describe TestDoubles::AdditionOrganizer do
|
5
|
-
it
|
4
|
+
RSpec.describe TestDoubles::AdditionOrganizer do
|
5
|
+
it 'Adds 1, 2 and 3 to the initial value of 1' do
|
6
6
|
result = TestDoubles::AdditionOrganizer.call(1)
|
7
|
-
number = result.fetch(:
|
7
|
+
number = result.fetch(:number)
|
8
8
|
|
9
9
|
expect(number).to eq(7)
|
10
10
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe 'Action after_actions' do
|
5
|
+
describe 'works with simple organizers - from outside' do
|
6
|
+
it 'can be used to inject code block before each action' do
|
7
|
+
TestDoubles::AdditionOrganizer.after_actions =
|
8
|
+
lambda do |ctx|
|
9
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
|
10
|
+
end
|
11
|
+
|
12
|
+
result = TestDoubles::AdditionOrganizer.call(0)
|
13
|
+
|
14
|
+
expect(result.fetch(:number)).to eq(4)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'works with iterator' do
|
18
|
+
TestDoubles::TestIterate.after_actions = [
|
19
|
+
lambda do |ctx|
|
20
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
|
21
|
+
end
|
22
|
+
]
|
23
|
+
|
24
|
+
result = TestDoubles::TestIterate.call(:number => 0,
|
25
|
+
:counters => [1, 2, 3, 4])
|
26
|
+
|
27
|
+
expect(result).to be_success
|
28
|
+
expect(result.number).to eq(-4)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'can be added to organizers declaratively' do
|
33
|
+
module AfterActions
|
34
|
+
class AdditionOrganizer
|
35
|
+
extend LightService::Organizer
|
36
|
+
after_actions (lambda do |ctx|
|
37
|
+
if ctx.current_action == TestDoubles::AddsOneAction
|
38
|
+
ctx.number -= 2
|
39
|
+
end
|
40
|
+
end),
|
41
|
+
(lambda do |ctx|
|
42
|
+
if ctx.current_action == TestDoubles::AddsThreeAction
|
43
|
+
ctx.number -= 3
|
44
|
+
end
|
45
|
+
end)
|
46
|
+
|
47
|
+
def self.call(number)
|
48
|
+
with(:number => number).reduce(actions)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.actions
|
52
|
+
[
|
53
|
+
TestDoubles::AddsOneAction,
|
54
|
+
TestDoubles::AddsTwoAction,
|
55
|
+
TestDoubles::AddsThreeAction
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'accepts after_actions hook lambdas from organizer' do
|
62
|
+
result = AfterActions::AdditionOrganizer.call(0)
|
63
|
+
|
64
|
+
expect(result.fetch(:number)).to eq(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe 'Action before_actions' do
|
5
|
+
describe 'works with simple organizers - from outside' do
|
6
|
+
it 'can be used to inject code block before each action' do
|
7
|
+
TestDoubles::AdditionOrganizer.before_actions =
|
8
|
+
lambda do |ctx|
|
9
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
|
10
|
+
end
|
11
|
+
|
12
|
+
result = TestDoubles::AdditionOrganizer.call(0)
|
13
|
+
|
14
|
+
expect(result.fetch(:number)).to eq(4)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'works with iterator' do
|
18
|
+
TestDoubles::TestIterate.before_actions = [
|
19
|
+
lambda do |ctx|
|
20
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
|
21
|
+
end
|
22
|
+
]
|
23
|
+
|
24
|
+
result = TestDoubles::TestIterate.call(:number => 0,
|
25
|
+
:counters => [1, 2, 3, 4])
|
26
|
+
|
27
|
+
expect(result).to be_success
|
28
|
+
expect(result.number).to eq(-4)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'can be added to organizers declaratively' do
|
33
|
+
module BeforeActions
|
34
|
+
class AdditionOrganizer
|
35
|
+
extend LightService::Organizer
|
36
|
+
before_actions (lambda do |ctx|
|
37
|
+
if ctx.current_action == TestDoubles::AddsOneAction
|
38
|
+
ctx.number -= 2
|
39
|
+
end
|
40
|
+
end),
|
41
|
+
(lambda do |ctx|
|
42
|
+
if ctx.current_action == TestDoubles::AddsThreeAction
|
43
|
+
ctx.number -= 3
|
44
|
+
end
|
45
|
+
end)
|
46
|
+
|
47
|
+
def self.call(number)
|
48
|
+
with(:number => number).reduce(actions)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.actions
|
52
|
+
[
|
53
|
+
TestDoubles::AddsOneAction,
|
54
|
+
TestDoubles::AddsTwoAction,
|
55
|
+
TestDoubles::AddsThreeAction
|
56
|
+
]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'accepts before_actions hook lambdas from organizer' do
|
62
|
+
result = BeforeActions::AdditionOrganizer.call(0)
|
63
|
+
|
64
|
+
expect(result.fetch(:number)).to eq(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'works with callbacks' do
|
69
|
+
it 'can interact with actions from the outside' do
|
70
|
+
TestDoubles::TestWithCallback.before_actions = [
|
71
|
+
lambda do |ctx|
|
72
|
+
if ctx.current_action == TestDoubles::AddToTotalAction
|
73
|
+
ctx.total -= 1000
|
74
|
+
end
|
75
|
+
end
|
76
|
+
]
|
77
|
+
result = TestDoubles::TestWithCallback.call
|
78
|
+
|
79
|
+
expect(result.counter).to eq(3)
|
80
|
+
expect(result.total).to eq(-2994)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'can halt all execution with a raised error' do
|
85
|
+
it 'does not call the rest of the callback steps' do
|
86
|
+
class SkipContextError < StandardError
|
87
|
+
attr_reader :ctx
|
88
|
+
|
89
|
+
def initialize(msg, ctx)
|
90
|
+
@ctx = ctx
|
91
|
+
super(msg)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
TestDoubles::TestWithCallback.before_actions = [
|
95
|
+
lambda do |ctx|
|
96
|
+
if ctx.current_action == TestDoubles::IncrementCountAction
|
97
|
+
ctx.total -= 1000
|
98
|
+
raise SkipContextError.new("stop context now", ctx)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
]
|
102
|
+
begin
|
103
|
+
TestDoubles::TestWithCallback.call
|
104
|
+
rescue SkipContextError => e
|
105
|
+
expect(e.ctx).not_to be_empty
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -10,18 +10,18 @@ describe LightService::Orchestrator do
|
|
10
10
|
with(:number => 1).reduce([
|
11
11
|
TestDoubles::SkipAllAction,
|
12
12
|
reduce_until(->(ctx) { ctx.number == 3 },
|
13
|
-
TestDoubles::
|
13
|
+
TestDoubles::AddsOneAction)
|
14
14
|
])
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.run_skip_after
|
18
18
|
with(:number => 1).reduce([
|
19
|
-
TestDoubles::
|
19
|
+
TestDoubles::AddsOneAction,
|
20
20
|
reduce_until(->(ctx) { ctx.number == 3 }, [
|
21
|
-
TestDoubles::
|
21
|
+
TestDoubles::AddsOneAction
|
22
22
|
]),
|
23
23
|
TestDoubles::SkipAllAction,
|
24
|
-
TestDoubles::
|
24
|
+
TestDoubles::AddsOneAction
|
25
25
|
])
|
26
26
|
end
|
27
27
|
|
@@ -29,8 +29,8 @@ describe LightService::Orchestrator do
|
|
29
29
|
with(:number => 1).reduce([
|
30
30
|
TestDoubles::FailureAction,
|
31
31
|
reduce_until(->(ctx) { ctx[:number] == 3 },
|
32
|
-
TestDoubles::
|
33
|
-
TestDoubles::
|
32
|
+
TestDoubles::AddsOneAction),
|
33
|
+
TestDoubles::AddsOneAction
|
34
34
|
])
|
35
35
|
end
|
36
36
|
end
|
@@ -13,10 +13,10 @@ describe LightService::Orchestrator do
|
|
13
13
|
|
14
14
|
def self.steps
|
15
15
|
[
|
16
|
-
TestDoubles::
|
16
|
+
TestDoubles::AddsOneAction,
|
17
17
|
execute(->(ctx) { ctx.number += 1 }),
|
18
18
|
execute(->(ctx) { ctx[:something] = 'hello' }),
|
19
|
-
TestDoubles::
|
19
|
+
TestDoubles::AddsOneAction
|
20
20
|
]
|
21
21
|
end
|
22
22
|
end
|
@@ -10,14 +10,14 @@ describe LightService::Orchestrator do
|
|
10
10
|
def self.run(context)
|
11
11
|
with(context).reduce([
|
12
12
|
iterate(:numbers, [
|
13
|
-
TestDoubles::
|
13
|
+
TestDoubles::AddsOneAction
|
14
14
|
])
|
15
15
|
])
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.run_single(context)
|
19
19
|
with(context).reduce([
|
20
|
-
iterate(:numbers, TestDoubles::
|
20
|
+
iterate(:numbers, TestDoubles::AddsOneAction)
|
21
21
|
])
|
22
22
|
end
|
23
23
|
end
|
@@ -15,7 +15,7 @@ describe LightService::Orchestrator do
|
|
15
15
|
it 'responds to both actions and organizers' do
|
16
16
|
result = OrchestratorTestReduce.run({ :number => 0 }, [
|
17
17
|
TestDoubles::AddTwoOrganizer,
|
18
|
-
TestDoubles::
|
18
|
+
TestDoubles::AddsOneAction
|
19
19
|
])
|
20
20
|
|
21
21
|
expect(result).to be_success
|
@@ -26,7 +26,7 @@ describe LightService::Orchestrator do
|
|
26
26
|
result = OrchestratorTestReduce.run({ :number => 0 }, [
|
27
27
|
TestDoubles::AddTwoOrganizer,
|
28
28
|
TestDoubles::FailureAction,
|
29
|
-
TestDoubles::
|
29
|
+
TestDoubles::AddsOneAction
|
30
30
|
])
|
31
31
|
|
32
32
|
expect(result).not_to be_success
|
@@ -13,9 +13,9 @@ RSpec.describe LightService::Organizer do
|
|
13
13
|
|
14
14
|
def self.actions
|
15
15
|
[
|
16
|
-
TestDoubles::
|
16
|
+
TestDoubles::AddsOneAction,
|
17
17
|
reduce_if(->(ctx) { ctx.number == 1 },
|
18
|
-
TestDoubles::
|
18
|
+
TestDoubles::AddsOneAction)
|
19
19
|
]
|
20
20
|
end
|
21
21
|
end
|
@@ -29,11 +29,11 @@ RSpec.describe LightService::Organizer do
|
|
29
29
|
expect(result.fetch(:number)).to eq(2)
|
30
30
|
expect(result[:logger].logs).to eq(
|
31
31
|
[{
|
32
|
-
:action => TestDoubles::
|
32
|
+
:action => TestDoubles::AddsOneAction,
|
33
33
|
:before => 0,
|
34
34
|
:after => 1
|
35
35
|
}, {
|
36
|
-
:action => TestDoubles::
|
36
|
+
:action => TestDoubles::AddsOneAction,
|
37
37
|
:before => 1,
|
38
38
|
:after => 2
|
39
39
|
}]
|
@@ -9,7 +9,7 @@ RSpec.describe LightService::Organizer do
|
|
9
9
|
.reduce([
|
10
10
|
TestDoubles::SkipAllAction,
|
11
11
|
reduce_until(->(ctx) { ctx.number == 3 },
|
12
|
-
TestDoubles::
|
12
|
+
TestDoubles::AddsOneAction)
|
13
13
|
])
|
14
14
|
end
|
15
15
|
end
|
@@ -19,12 +19,12 @@ RSpec.describe LightService::Organizer do
|
|
19
19
|
def self.call
|
20
20
|
with(:number => 1)
|
21
21
|
.reduce([
|
22
|
-
TestDoubles::
|
22
|
+
TestDoubles::AddsOneAction,
|
23
23
|
reduce_until(->(ctx) { ctx.number == 3 }, [
|
24
|
-
TestDoubles::
|
24
|
+
TestDoubles::AddsOneAction
|
25
25
|
]),
|
26
26
|
TestDoubles::SkipAllAction,
|
27
|
-
TestDoubles::
|
27
|
+
TestDoubles::AddsOneAction
|
28
28
|
])
|
29
29
|
end
|
30
30
|
end
|
@@ -36,8 +36,8 @@ RSpec.describe LightService::Organizer do
|
|
36
36
|
.reduce([
|
37
37
|
TestDoubles::FailureAction,
|
38
38
|
reduce_until(->(ctx) { ctx[:number] == 3 },
|
39
|
-
TestDoubles::
|
40
|
-
TestDoubles::
|
39
|
+
TestDoubles::AddsOneAction),
|
40
|
+
TestDoubles::AddsOneAction
|
41
41
|
])
|
42
42
|
end
|
43
43
|
end
|
@@ -11,10 +11,10 @@ RSpec.describe LightService::Organizer do
|
|
11
11
|
|
12
12
|
def self.steps
|
13
13
|
[
|
14
|
-
TestDoubles::
|
14
|
+
TestDoubles::AddsOneAction,
|
15
15
|
execute(->(ctx) { ctx.number += 1 }),
|
16
16
|
execute(->(ctx) { ctx[:something] = 'hello' }),
|
17
|
-
TestDoubles::
|
17
|
+
TestDoubles::AddsOneAction
|
18
18
|
]
|
19
19
|
end
|
20
20
|
end
|
@@ -2,33 +2,19 @@ require 'spec_helper'
|
|
2
2
|
require 'test_doubles'
|
3
3
|
|
4
4
|
RSpec.describe LightService::Organizer do
|
5
|
-
class TestIterate
|
6
|
-
extend LightService::Organizer
|
7
|
-
|
8
|
-
def self.call(context)
|
9
|
-
with(context)
|
10
|
-
.reduce([iterate(:numbers,
|
11
|
-
[TestDoubles::AddOneAction])])
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.call_single(context)
|
15
|
-
with(context)
|
16
|
-
.reduce([iterate(:numbers,
|
17
|
-
TestDoubles::AddOneAction)])
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
5
|
let(:empty_context) { LightService::Context.make }
|
22
6
|
|
23
7
|
it 'reduces each item of a collection and singularizes the collection key' do
|
24
|
-
result = TestIterate.call(:
|
8
|
+
result = TestDoubles::TestIterate.call(:number => 1,
|
9
|
+
:counters => [1, 2, 3, 4])
|
25
10
|
|
26
11
|
expect(result).to be_success
|
27
12
|
expect(result.number).to eq(5)
|
28
13
|
end
|
29
14
|
|
30
15
|
it 'accepts a single action or organizer' do
|
31
|
-
result = TestIterate.call_single(:
|
16
|
+
result = TestDoubles::TestIterate.call_single(:number => 1,
|
17
|
+
:counters => [1, 2, 3, 4])
|
32
18
|
|
33
19
|
expect(result).to be_success
|
34
20
|
expect(result.number).to eq(5)
|
@@ -37,7 +23,7 @@ RSpec.describe LightService::Organizer do
|
|
37
23
|
it 'will not iterate over a failed context' do
|
38
24
|
empty_context.fail!('Something bad happened')
|
39
25
|
|
40
|
-
result = TestIterate.call(empty_context)
|
26
|
+
result = TestDoubles::TestIterate.call(empty_context)
|
41
27
|
|
42
28
|
expect(result).to be_failure
|
43
29
|
end
|
@@ -45,7 +31,7 @@ RSpec.describe LightService::Organizer do
|
|
45
31
|
it 'does not iterate over a skipped context' do
|
46
32
|
empty_context.skip_remaining!('No more needed')
|
47
33
|
|
48
|
-
result = TestIterate.call(empty_context)
|
34
|
+
result = TestDoubles::TestIterate.call(empty_context)
|
49
35
|
expect(result).to be_success
|
50
36
|
end
|
51
37
|
end
|
@@ -2,68 +2,9 @@ require 'spec_helper'
|
|
2
2
|
require 'test_doubles'
|
3
3
|
|
4
4
|
RSpec.describe LightService::Organizer do
|
5
|
-
class TestWithCallback
|
6
|
-
extend LightService::Organizer
|
7
|
-
|
8
|
-
def self.call(context = {})
|
9
|
-
with(context).reduce(actions)
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.actions
|
13
|
-
[
|
14
|
-
SetUpContextAction,
|
15
|
-
with_callback(IterateCollectionAction,
|
16
|
-
[IncrementCountAction,
|
17
|
-
AddToTotalAction])
|
18
|
-
]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class SetUpContextAction
|
23
|
-
extend LightService::Action
|
24
|
-
promises :numbers, :counter, :total
|
25
|
-
|
26
|
-
executed do |ctx|
|
27
|
-
ctx.numbers = [1, 2, 3]
|
28
|
-
ctx.counter = 0
|
29
|
-
ctx.total = 0
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class IterateCollectionAction
|
34
|
-
extend LightService::Action
|
35
|
-
expects :numbers, :callback
|
36
|
-
promises :number
|
37
|
-
|
38
|
-
executed do |ctx|
|
39
|
-
ctx.numbers.each do |number|
|
40
|
-
ctx.number = number
|
41
|
-
ctx.callback.call(ctx)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class IncrementCountAction
|
47
|
-
extend LightService::Action
|
48
|
-
expects :counter
|
49
|
-
|
50
|
-
executed do |ctx|
|
51
|
-
ctx.counter = ctx.counter + 1
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class AddToTotalAction
|
56
|
-
extend LightService::Action
|
57
|
-
expects :number, :total
|
58
|
-
|
59
|
-
executed do |ctx|
|
60
|
-
ctx.total += ctx.number
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
5
|
describe 'a simple case with a single callback' do
|
65
6
|
it 'calls the actions defined with callback' do
|
66
|
-
result = TestWithCallback.call
|
7
|
+
result = TestDoubles::TestWithCallback.call
|
67
8
|
|
68
9
|
expect(result.counter).to eq(3)
|
69
10
|
expect(result.total).to eq(6)
|
@@ -83,9 +24,9 @@ RSpec.describe LightService::Organizer do
|
|
83
24
|
SetUpNestedContextAction,
|
84
25
|
with_callback(IterateOuterCollectionAction,
|
85
26
|
[IncrementOuterCountAction,
|
86
|
-
with_callback(IterateCollectionAction,
|
87
|
-
[IncrementCountAction,
|
88
|
-
AddToTotalAction])])
|
27
|
+
with_callback(TestDoubles::IterateCollectionAction,
|
28
|
+
[TestDoubles::IncrementCountAction,
|
29
|
+
TestDoubles::AddToTotalAction])])
|
89
30
|
]
|
90
31
|
end
|
91
32
|
end
|
@@ -150,9 +91,9 @@ RSpec.describe LightService::Organizer do
|
|
150
91
|
|
151
92
|
def self.actions
|
152
93
|
[
|
153
|
-
SetUpContextAction,
|
154
|
-
with_callback(IterateCollectionAction,
|
155
|
-
[IncrementCountAction,
|
94
|
+
TestDoubles::SetUpContextAction,
|
95
|
+
with_callback(TestDoubles::IterateCollectionAction,
|
96
|
+
[TestDoubles::IncrementCountAction,
|
156
97
|
TestDoubles::FailureAction])
|
157
98
|
]
|
158
99
|
end
|
@@ -6,10 +6,7 @@ require_relative 'tax/provides_free_shipping_action'
|
|
6
6
|
|
7
7
|
describe CalculatesTax do
|
8
8
|
let(:order) { double('order') }
|
9
|
-
let(:ctx)
|
10
|
-
double('ctx', :keys => [:user],
|
11
|
-
:failure? => false, :skip_remaining? => false)
|
12
|
-
end
|
9
|
+
let(:ctx) { LightService::Context.make(:user => nil) }
|
13
10
|
|
14
11
|
it "calls the actions in order" do
|
15
12
|
allow(LightService::Context).to receive(:make)
|
data/spec/test_doubles.rb
CHANGED
@@ -30,21 +30,10 @@ module TestDoubles
|
|
30
30
|
executed(&:fail!)
|
31
31
|
end
|
32
32
|
|
33
|
-
class AddOneAction
|
34
|
-
extend LightService::Action
|
35
|
-
expects :number
|
36
|
-
promises :number
|
37
|
-
|
38
|
-
executed do |ctx|
|
39
|
-
ctx.number += 1
|
40
|
-
ctx.message = 'Added 1'
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
33
|
class AddTwoOrganizer
|
45
34
|
extend LightService::Organizer
|
46
35
|
def self.call(context)
|
47
|
-
with(context).reduce([
|
36
|
+
with(context).reduce([AddsOneAction, AddsOneAction])
|
48
37
|
end
|
49
38
|
end
|
50
39
|
|
@@ -256,10 +245,9 @@ module TestDoubles
|
|
256
245
|
class AddsThreeAction
|
257
246
|
extend LightService::Action
|
258
247
|
expects :number
|
259
|
-
promises :product
|
260
248
|
|
261
249
|
executed do |context|
|
262
|
-
context.
|
250
|
+
context.number += 3
|
263
251
|
end
|
264
252
|
end
|
265
253
|
|
@@ -341,4 +329,79 @@ module TestDoubles
|
|
341
329
|
|
342
330
|
executed { |_ctx| }
|
343
331
|
end
|
332
|
+
|
333
|
+
class TestIterate
|
334
|
+
extend LightService::Organizer
|
335
|
+
|
336
|
+
def self.call(context)
|
337
|
+
with(context)
|
338
|
+
.reduce([iterate(:counters,
|
339
|
+
[TestDoubles::AddsOneAction])])
|
340
|
+
end
|
341
|
+
|
342
|
+
def self.call_single(context)
|
343
|
+
with(context)
|
344
|
+
.reduce([iterate(:counters,
|
345
|
+
TestDoubles::AddsOneAction)])
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
class TestWithCallback
|
350
|
+
extend LightService::Organizer
|
351
|
+
|
352
|
+
def self.call(context = {})
|
353
|
+
with(context).reduce(actions)
|
354
|
+
end
|
355
|
+
|
356
|
+
def self.actions
|
357
|
+
[
|
358
|
+
SetUpContextAction,
|
359
|
+
with_callback(IterateCollectionAction,
|
360
|
+
[IncrementCountAction,
|
361
|
+
AddToTotalAction])
|
362
|
+
]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
class SetUpContextAction
|
367
|
+
extend LightService::Action
|
368
|
+
promises :numbers, :counter, :total
|
369
|
+
|
370
|
+
executed do |ctx|
|
371
|
+
ctx.numbers = [1, 2, 3]
|
372
|
+
ctx.counter = 0
|
373
|
+
ctx.total = 0
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class IterateCollectionAction
|
378
|
+
extend LightService::Action
|
379
|
+
expects :numbers, :callback
|
380
|
+
promises :number
|
381
|
+
|
382
|
+
executed do |ctx|
|
383
|
+
ctx.numbers.each do |number|
|
384
|
+
ctx.number = number
|
385
|
+
ctx.callback.call(ctx)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class IncrementCountAction
|
391
|
+
extend LightService::Action
|
392
|
+
expects :counter
|
393
|
+
|
394
|
+
executed do |ctx|
|
395
|
+
ctx.counter = ctx.counter + 1
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
class AddToTotalAction
|
400
|
+
extend LightService::Action
|
401
|
+
expects :number, :total
|
402
|
+
|
403
|
+
executed do |ctx|
|
404
|
+
ctx.total += ctx.number
|
405
|
+
end
|
406
|
+
end
|
344
407
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: light-service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Attila Domokos
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.16.1
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
54
|
+
version: 0.16.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rubocop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
61
|
+
version: '0.53'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
68
|
+
version: '0.53'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pry
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -133,7 +133,9 @@ files:
|
|
133
133
|
- resources/organizer_and_actions.png
|
134
134
|
- resources/skip_actions.png
|
135
135
|
- spec/acceptance/add_numbers_spec.rb
|
136
|
+
- spec/acceptance/after_actions_spec.rb
|
136
137
|
- spec/acceptance/around_each_spec.rb
|
138
|
+
- spec/acceptance/before_actions_spec.rb
|
137
139
|
- spec/acceptance/fail_spec.rb
|
138
140
|
- spec/acceptance/include_warning_spec.rb
|
139
141
|
- spec/acceptance/log_from_organizer_spec.rb
|
@@ -204,7 +206,9 @@ specification_version: 4
|
|
204
206
|
summary: A service skeleton with an emphasis on simplicity
|
205
207
|
test_files:
|
206
208
|
- spec/acceptance/add_numbers_spec.rb
|
209
|
+
- spec/acceptance/after_actions_spec.rb
|
207
210
|
- spec/acceptance/around_each_spec.rb
|
211
|
+
- spec/acceptance/before_actions_spec.rb
|
208
212
|
- spec/acceptance/fail_spec.rb
|
209
213
|
- spec/acceptance/include_warning_spec.rb
|
210
214
|
- spec/acceptance/log_from_organizer_spec.rb
|