light-service 0.10.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +12 -10
- data/Appraisals +4 -0
- data/README.md +39 -20
- data/RELEASES.md +17 -0
- data/gemfiles/activesupport_6.gemfile +8 -0
- data/lib/light-service.rb +1 -0
- data/lib/light-service/context.rb +4 -1
- data/lib/light-service/localization_adapter.rb +1 -1
- data/lib/light-service/organizer.rb +32 -0
- data/lib/light-service/organizer/with_reducer.rb +4 -5
- data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +2 -2
- data/lib/light-service/testing/context_factory.rb +12 -7
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +5 -4
- data/spec/acceptance/after_actions_spec.rb +13 -0
- data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
- data/spec/acceptance/fail_spec.rb +42 -16
- data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
- data/spec/acceptance/organizer/execute_spec.rb +1 -1
- data/spec/acceptance/organizer/reduce_if_spec.rb +32 -0
- data/spec/acceptance/testing/context_factory_spec.rb +12 -3
- data/spec/organizer_spec.rb +37 -14
- data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -1
- data/spec/test_doubles.rb +93 -0
- data/spec/testing/context_factory_spec.rb +20 -0
- metadata +32 -15
- data/gemfiles/activesupport_3.gemfile.lock +0 -76
- data/gemfiles/activesupport_4.gemfile.lock +0 -82
- data/gemfiles/activesupport_5.gemfile.lock +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1e9dfb5cf61024afa28719b84e160c9580fa39c99eda19b73cdb0cced9017da2
|
4
|
+
data.tar.gz: 7f389c208b3b06000c84859d595d9b49acf8dfe8d370736ee564a1ae8ef8b5d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1b7792d344efe1aab4297f8774649590b8e0336a44082da31a167a62b75a039b7f89759923beb9a463f6c017b0387297efc815f357deae31270b54e2c1c1bcc
|
7
|
+
data.tar.gz: 0d1d339a23d2d7b39bd5403d81fe1e1519a89eec22fd64f00931d9dc49a81c5476f67e6dd28591e0e88c55b1378d1155ed0d1700100569aa9b43a1922c860f3e
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
require: rubocop-performance
|
2
|
+
|
1
3
|
AllCops:
|
4
|
+
TargetRubyVersion: 2.3
|
2
5
|
Exclude:
|
3
6
|
- 'lib/light-service.rb'
|
4
7
|
- 'vendor/bundle/**/*'
|
@@ -46,3 +49,6 @@ Metrics/BlockLength:
|
|
46
49
|
|
47
50
|
Layout/TrailingBlankLines:
|
48
51
|
Enabled: false
|
52
|
+
|
53
|
+
Layout/EndOfLine:
|
54
|
+
EnforcedStyle: lf
|
data/.travis.yml
CHANGED
@@ -4,16 +4,15 @@ env:
|
|
4
4
|
- RUN_COVERAGE_REPORT=true
|
5
5
|
|
6
6
|
rvm:
|
7
|
-
- 2.
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.
|
7
|
+
- 2.4.2
|
8
|
+
- 2.5.3
|
9
|
+
- 2.6.0
|
10
|
+
- 2.7.0
|
11
11
|
|
12
12
|
before_install:
|
13
13
|
- 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
|
14
14
|
- gem install bundler
|
15
|
-
- bundle
|
16
|
-
- bundle install --path vendor/bundle
|
15
|
+
- bundle install --clean --path vendor/bundle
|
17
16
|
|
18
17
|
# uncomment this line if your project needs to run something other than `rake`:
|
19
18
|
script:
|
@@ -24,10 +23,13 @@ gemfile:
|
|
24
23
|
- gemfiles/activesupport_3.gemfile
|
25
24
|
- gemfiles/activesupport_4.gemfile
|
26
25
|
- gemfiles/activesupport_5.gemfile
|
26
|
+
- gemfiles/activesupport_6.gemfile
|
27
27
|
|
28
28
|
matrix:
|
29
29
|
exclude:
|
30
|
-
- rvm: 2.
|
31
|
-
gemfile: gemfiles/
|
32
|
-
- rvm: 2.
|
33
|
-
gemfile: gemfiles/
|
30
|
+
- rvm: 2.4.2
|
31
|
+
gemfile: gemfiles/activesupport_6.gemfile
|
32
|
+
- rvm: 2.7.0
|
33
|
+
gemfile: gemfiles/activesupport_3.gemfile
|
34
|
+
- rvm: 2.7.0
|
35
|
+
gemfile: gemfiles/activesupport_4.gemfile
|
data/Appraisals
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
![LightService](https://raw.githubusercontent.com/adomokos/light-service/master/resources/light-service.png)
|
2
2
|
|
3
3
|
[![Gem Version](https://img.shields.io/gem/v/light-service.svg)](https://rubygems.org/gems/light-service)
|
4
|
-
[![Build Status](https://secure.travis-ci.org/adomokos/light-service.
|
5
|
-
[![Code Climate](https://codeclimate.com/github/adomokos/light-service.
|
6
|
-
[![Dependency Status](https://beta.gemnasium.com/badges/github.com/adomokos/light-service.svg)](https://beta.gemnasium.com/projects/github.com/adomokos/light-service)
|
4
|
+
[![Build Status](https://secure.travis-ci.org/adomokos/light-service.svg)](http://travis-ci.org/adomokos/light-service)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/adomokos/light-service.svg)](https://codeclimate.com/github/adomokos/light-service)
|
7
6
|
[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
|
7
|
+
[![Download Count](https://ruby-gem-downloads-badge.herokuapp.com/light-service?type=total)](https://rubygems.org/gems/light-service)
|
8
8
|
|
9
9
|
<br><br>
|
10
10
|
|
@@ -79,7 +79,7 @@ Wouldn't it be nice to see this instead?
|
|
79
79
|
(
|
80
80
|
LooksUpTaxPercentage,
|
81
81
|
CalculatesOrderTax,
|
82
|
-
|
82
|
+
ProvidesFreeShipping
|
83
83
|
)
|
84
84
|
```
|
85
85
|
|
@@ -257,12 +257,12 @@ Check out this example:
|
|
257
257
|
|
258
258
|
```ruby
|
259
259
|
class LogDuration
|
260
|
-
def self.call(
|
260
|
+
def self.call(context)
|
261
261
|
start_time = Time.now
|
262
262
|
result = yield
|
263
263
|
duration = Time.now - start_time
|
264
264
|
LightService::Configuration.logger.info(
|
265
|
-
:action =>
|
265
|
+
:action => context.current_action,
|
266
266
|
:duration => duration
|
267
267
|
)
|
268
268
|
|
@@ -295,14 +295,16 @@ Consider this code:
|
|
295
295
|
class SomeOrganizer
|
296
296
|
extend LightService::Organizer
|
297
297
|
|
298
|
-
def call(ctx)
|
298
|
+
def self.call(ctx)
|
299
299
|
with(ctx).reduce(actions)
|
300
300
|
end
|
301
301
|
|
302
|
-
def actions
|
303
|
-
|
304
|
-
|
305
|
-
|
302
|
+
def self.actions
|
303
|
+
[
|
304
|
+
OneAction,
|
305
|
+
TwoAction,
|
306
|
+
ThreeAction
|
307
|
+
]
|
306
308
|
end
|
307
309
|
end
|
308
310
|
|
@@ -346,14 +348,16 @@ class SomeOrganizer
|
|
346
348
|
end
|
347
349
|
end)
|
348
350
|
|
349
|
-
def call(ctx)
|
351
|
+
def self.call(ctx)
|
350
352
|
with(ctx).reduce(actions)
|
351
353
|
end
|
352
354
|
|
353
|
-
def actions
|
354
|
-
|
355
|
-
|
356
|
-
|
355
|
+
def self.actions
|
356
|
+
[
|
357
|
+
OneAction,
|
358
|
+
TwoAction,
|
359
|
+
ThreeAction
|
360
|
+
]
|
357
361
|
end
|
358
362
|
end
|
359
363
|
|
@@ -452,9 +456,9 @@ class AnOrganizer
|
|
452
456
|
|
453
457
|
def self.call(order)
|
454
458
|
with(:order => order).reduce(
|
455
|
-
|
456
|
-
|
457
|
-
|
459
|
+
AnAction,
|
460
|
+
AnotherAction,
|
461
|
+
)
|
458
462
|
end
|
459
463
|
end
|
460
464
|
|
@@ -535,6 +539,15 @@ I, [DATE] INFO -- : [LightService] - ;-) <TestDoubles::MakesLatteAction> has de
|
|
535
539
|
I, [DATE] INFO -- : [LightService] - context message: Can't make a latte with a fatty milk like that!
|
536
540
|
```
|
537
541
|
|
542
|
+
You can specify the logger on the organizer level, so the organizer does not use the global logger.
|
543
|
+
|
544
|
+
```ruby
|
545
|
+
class FooOrganizer
|
546
|
+
extend LightService::Organizer
|
547
|
+
log_with Logger.new("/my/special.log")
|
548
|
+
end
|
549
|
+
```
|
550
|
+
|
538
551
|
## Error Codes
|
539
552
|
You can add some more structure to your error handling by taking advantage of error codes in the context.
|
540
553
|
Normally, when something goes wrong in your actions, you fail the process by setting the context to failure:
|
@@ -746,13 +759,15 @@ end
|
|
746
759
|
|
747
760
|
This code is much easier to reason about, it's less noisy and it captures the goal of LightService well: simple, declarative code that's easy to understand.
|
748
761
|
|
749
|
-
The
|
762
|
+
The 7 different orchestrator constructs an organizer can have:
|
750
763
|
|
751
764
|
1. `reduce_until`
|
752
765
|
2. `reduce_if`
|
753
766
|
3. `iterate`
|
754
767
|
4. `execute`
|
755
768
|
5. `with_callback`
|
769
|
+
6. `add_to_context`
|
770
|
+
7. `add_aliases`
|
756
771
|
|
757
772
|
`reduce_until` behaves like a while loop in imperative languages, it iterates until the provided predicate in the lambda evaluates to true. Take a look at [this acceptance test](spec/acceptance/organizer/reduce_until_spec.rb) to see how it's used.
|
758
773
|
|
@@ -764,6 +779,10 @@ To take advantage of another organizer or action, you might need to tweak the co
|
|
764
779
|
|
765
780
|
Use `with_callback` when you want to execute actions with a deferred and controlled callback. It works similar to a Sax parser, I've used it for processing large files. The advantage of it is not having to keep large amount of data in memory. See [this acceptance test](spec/acceptance/organizer/with_callback_spec.rb) as a working example.
|
766
781
|
|
782
|
+
`add_to_context` can add key-value pairs on the fly to the context. This functionality is useful when you need a value injected into the context under a specific key right before the subsequent actions are executed. [This test](spec/acceptance/organizer/add_to_context_spec.rb) describes its functionality.
|
783
|
+
|
784
|
+
Your action needs a certain key in the context but it's under a different one? Use the function `add_aliases` to alias an existing key in the context under the desired key. Take a look at [this test](spec/acceptance/organizer/add_aliases_spec.rb) to see an example.
|
785
|
+
|
767
786
|
## ContextFactory for Faster Action Testing
|
768
787
|
|
769
788
|
As the complexity of your workflow increases, you will find yourself spending more and more time creating a context (LightService::Context it is) for your action tests. Some of this code can be reused by clever factories, but still, you are using a context that is artificial, and can be different from what the previous actions produced. This is especially true, when you use LightService in ETLs, where you start out with initial data and your actions are mutating its state.
|
data/RELEASES.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
A brief list of new features and changes introduced with the specified version.
|
2
2
|
|
3
|
+
### 0.13.0
|
4
|
+
* [Add 'add_to_context' and 'add_aliases'](https://github.com/adomokos/light-service/pull/172)
|
5
|
+
* Updating Ruby compatibility, minor fixes
|
6
|
+
|
7
|
+
### 0.12.0
|
8
|
+
* [Per organizer logger](https://github.com/adomokos/light-service/pull/162)
|
9
|
+
* [Fix 'fail_and_return!' not accepting 'error_code' option](https://github.com/adomokos/light-service/pull/168)
|
10
|
+
|
11
|
+
### 0.11.0
|
12
|
+
* [Switch to 'each_with_object' in WithReducer](https://github.com/adomokos/light-service/pull/149).
|
13
|
+
|
14
|
+
### 0.10.3
|
15
|
+
* [Adding ContextFactory](https://github.com/adomokos/light-service/pull/147).
|
16
|
+
|
17
|
+
### 0.10.2
|
18
|
+
* [Revert 0.10.1](https://github.com/adomokos/light-service/pull/146), it breaks tests in our apps :-(.
|
19
|
+
|
3
20
|
### 0.10.1
|
4
21
|
* [Fixing ContextFactory](https://github.com/adomokos/light-service/pull/141) for orchestrator methods in Organizers.
|
5
22
|
|
data/lib/light-service.rb
CHANGED
@@ -88,7 +88,7 @@ module LightService
|
|
88
88
|
|
89
89
|
def fail_and_return!(*args)
|
90
90
|
fail!(*args)
|
91
|
-
throw(:jump_when_failed
|
91
|
+
throw(:jump_when_failed)
|
92
92
|
end
|
93
93
|
|
94
94
|
def fail_with_rollback!(message = nil, error_code = nil)
|
@@ -115,8 +115,10 @@ module LightService
|
|
115
115
|
|
116
116
|
def define_accessor_methods_for_keys(keys)
|
117
117
|
return if keys.nil?
|
118
|
+
|
118
119
|
keys.each do |key|
|
119
120
|
next if respond_to?(key.to_sym)
|
121
|
+
|
120
122
|
define_singleton_method(key.to_s) { fetch(key) }
|
121
123
|
define_singleton_method("#{key}=") { |value| self[key] = value }
|
122
124
|
end
|
@@ -161,6 +163,7 @@ module LightService
|
|
161
163
|
|
162
164
|
def check_nil(value)
|
163
165
|
return 'nil' unless value
|
166
|
+
|
164
167
|
"'#{value}'"
|
165
168
|
end
|
166
169
|
end
|
@@ -56,6 +56,24 @@ module LightService
|
|
56
56
|
def with_callback(action, steps)
|
57
57
|
WithCallback.run(self, action, steps)
|
58
58
|
end
|
59
|
+
|
60
|
+
def log_with(logger)
|
61
|
+
@logger = logger
|
62
|
+
end
|
63
|
+
|
64
|
+
def logger
|
65
|
+
@logger
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_to_context(**args)
|
69
|
+
args.map do |key, value|
|
70
|
+
execute(->(ctx) { ctx[key.to_sym] = value })
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_aliases(args)
|
75
|
+
execute(->(ctx) { ctx.assign_aliases(ctx.aliases.merge(args)) })
|
76
|
+
end
|
59
77
|
end
|
60
78
|
|
61
79
|
module Macros
|
@@ -63,6 +81,8 @@ module LightService
|
|
63
81
|
@aliases = key_hash
|
64
82
|
end
|
65
83
|
|
84
|
+
# This looks like an accessor,
|
85
|
+
# but it's used as a macro in the Organizer
|
66
86
|
def before_actions(*logic)
|
67
87
|
self.before_actions = logic
|
68
88
|
end
|
@@ -71,6 +91,13 @@ module LightService
|
|
71
91
|
@before_actions = [logic].flatten
|
72
92
|
end
|
73
93
|
|
94
|
+
def append_before_actions(action)
|
95
|
+
@before_actions ||= []
|
96
|
+
@before_actions.push(action)
|
97
|
+
end
|
98
|
+
|
99
|
+
# This looks like an accessor,
|
100
|
+
# but it's used as a macro in the Organizer
|
74
101
|
def after_actions(*logic)
|
75
102
|
self.after_actions = logic
|
76
103
|
end
|
@@ -78,6 +105,11 @@ module LightService
|
|
78
105
|
def after_actions=(logic)
|
79
106
|
@after_actions = [logic].flatten
|
80
107
|
end
|
108
|
+
|
109
|
+
def append_after_actions(action)
|
110
|
+
@after_actions ||= []
|
111
|
+
@after_actions.push(action)
|
112
|
+
end
|
81
113
|
end
|
82
114
|
end
|
83
115
|
end
|
@@ -23,19 +23,18 @@ module LightService
|
|
23
23
|
|
24
24
|
def reduce(*actions)
|
25
25
|
raise "No action(s) were provided" if actions.empty?
|
26
|
+
|
26
27
|
actions.flatten!
|
27
28
|
|
28
|
-
actions.
|
29
|
+
actions.each_with_object(context) do |action, current_context|
|
29
30
|
begin
|
30
|
-
|
31
|
+
invoke_action(current_context, action)
|
31
32
|
rescue FailWithRollbackError
|
32
|
-
|
33
|
+
reduce_rollback(actions)
|
33
34
|
ensure
|
34
35
|
# For logging
|
35
36
|
yield(current_context, action) if block_given?
|
36
37
|
end
|
37
|
-
|
38
|
-
result
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
@@ -2,13 +2,17 @@ module LightService
|
|
2
2
|
module Organizer
|
3
3
|
class WithReducerFactory
|
4
4
|
def self.make(monitored_organizer)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
logger = monitored_organizer.logger ||
|
6
|
+
LightService::Configuration.logger
|
7
|
+
decorated = WithReducer.new
|
8
|
+
|
9
|
+
return decorated if logger.nil?
|
10
|
+
|
11
|
+
WithReducerLogDecorator.new(
|
12
|
+
monitored_organizer,
|
13
|
+
:decorated => decorated,
|
14
|
+
:logger => logger
|
15
|
+
)
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -5,10 +5,10 @@ module LightService
|
|
5
5
|
|
6
6
|
alias logged? logged
|
7
7
|
|
8
|
-
def initialize(organizer, decorated
|
8
|
+
def initialize(organizer, decorated: WithReducer.new, logger:)
|
9
9
|
@decorated = decorated
|
10
10
|
@organizer = organizer
|
11
|
-
@logger =
|
11
|
+
@logger = logger
|
12
12
|
@logged = false
|
13
13
|
end
|
14
14
|
|
@@ -8,23 +8,28 @@ module LightService
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def for(action)
|
11
|
-
@organizer.
|
11
|
+
@organizer.append_before_actions(
|
12
12
|
lambda do |ctx|
|
13
13
|
if ctx.current_action == action
|
14
|
+
# The last `:_before_actions` hook is for
|
15
|
+
# ContextFactory, remove it, so it won't
|
16
|
+
# be invoked again
|
17
|
+
ctx[:_before_actions].pop
|
18
|
+
|
14
19
|
throw(:return_ctx_from_execution, ctx)
|
15
20
|
end
|
16
21
|
end
|
17
|
-
|
22
|
+
)
|
18
23
|
|
19
24
|
self
|
20
25
|
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
# More than one arguments can be passed to the
|
28
|
+
# Organizer's #call method
|
29
|
+
def with(*args, &block)
|
30
|
+
catch(:return_ctx_from_execution) do
|
31
|
+
@organizer.call(*args, &block)
|
25
32
|
end
|
26
|
-
|
27
|
-
escaped
|
28
33
|
end
|
29
34
|
|
30
35
|
def initialize(organizer)
|