light-service 0.9.0 → 0.10.0
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.
- 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
|

|
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
|