light-service 0.15.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/project-build.yml +28 -0
- data/.rubocop.yml +117 -3
- data/.travis.yml +3 -8
- data/README.md +89 -37
- data/RELEASES.md +18 -1
- data/lib/generators/light_service/generator_utils.rb +0 -2
- data/lib/light-service/action.rb +61 -4
- data/lib/light-service/context/key_verifier.rb +22 -3
- data/lib/light-service/context.rb +10 -12
- data/lib/light-service/errors.rb +5 -0
- data/lib/light-service/orchestrator.rb +1 -1
- data/lib/light-service/organizer/reduce_case.rb +48 -0
- data/lib/light-service/organizer/reduce_if_else.rb +21 -0
- data/lib/light-service/organizer/with_reducer.rb +11 -14
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +2 -2
- data/lib/light-service/organizer.rb +20 -3
- data/lib/light-service/testing/context_factory.rb +2 -0
- data/lib/light-service/version.rb +1 -1
- data/lib/light-service.rb +2 -0
- data/light-service.gemspec +3 -2
- data/spec/acceptance/after_actions_spec.rb +17 -6
- data/spec/acceptance/around_each_spec.rb +15 -0
- data/spec/acceptance/before_actions_spec.rb +3 -9
- data/spec/acceptance/log_from_organizer_spec.rb +1 -1
- data/spec/acceptance/organizer/add_to_context_spec.rb +27 -0
- data/spec/acceptance/organizer/execute_with_add_to_context_spec.rb +28 -0
- data/spec/acceptance/organizer/reduce_case_spec.rb +53 -0
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +2 -0
- data/spec/action_optional_expected_keys_spec.rb +82 -0
- data/spec/context/inspect_spec.rb +5 -21
- data/spec/context_spec.rb +1 -1
- data/spec/lib/generators/action_generator_advanced_spec.rb +1 -1
- data/spec/lib/generators/action_generator_simple_spec.rb +1 -1
- data/spec/lib/generators/organizer_generator_advanced_spec.rb +1 -1
- data/spec/lib/generators/organizer_generator_simple_spec.rb +1 -1
- data/spec/sample/calculates_tax_spec.rb +0 -1
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +3 -1
- data/spec/test_doubles.rb +48 -5
- metadata +22 -13
- data/gemfiles/activesupport_4.gemfile +0 -7
- data/resources/orchestrators_deprecated.svg +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d713cae791654b29dfc20e3cbffe16094badcc49b8221ccff0765dbd479b40c2
|
4
|
+
data.tar.gz: 1e2d61c708b70466a904f8e07aa4ba4aa383bd405342efeb51014286209b2ebb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1d3f210ecbc0430bae9f573a945d599fcbf36a4acf26f5a49b9997c735dea8eba14f367e7c7a639d5cb8ff458feb5945ecd0078986140f763bb17b967947389
|
7
|
+
data.tar.gz: d2715e9fe494e4163efd52b4e2ce32011c3e275e8bdd1ba45e8ef1c2aff8705f2d2c994e02de0b8e20896ea5067ec75fb6a7ca333ac7f9b333544fcd82d2c7ca
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: CI Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ main ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ main ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ${{ matrix.os }}-latest
|
12
|
+
strategy:
|
13
|
+
fail-fast: false
|
14
|
+
matrix:
|
15
|
+
os: [ubuntu, macos]
|
16
|
+
ruby: [2.7.4, 3.0.2, 3.1.0]
|
17
|
+
gemfile: [activesupport_5, activesupport_6]
|
18
|
+
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
|
19
|
+
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
20
|
+
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v2
|
23
|
+
- uses: ruby/setup-ruby@v1
|
24
|
+
with:
|
25
|
+
ruby-version: ${{ matrix.ruby }}
|
26
|
+
- run: bundle install
|
27
|
+
- run: bundle exec rspec spec
|
28
|
+
- run: bundle exec rubocop
|
data/.rubocop.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require: rubocop-performance
|
1
|
+
# require: rubocop-performance
|
2
2
|
|
3
3
|
AllCops:
|
4
|
-
TargetRubyVersion: 2.
|
4
|
+
TargetRubyVersion: 2.7
|
5
5
|
Exclude:
|
6
6
|
- 'lib/light-service.rb'
|
7
7
|
- 'vendor/bundle/**/*'
|
@@ -47,8 +47,122 @@ Metrics/BlockLength:
|
|
47
47
|
Exclude:
|
48
48
|
- 'spec/**/*.rb'
|
49
49
|
|
50
|
-
Layout/
|
50
|
+
Layout/TrailingEmptyLines:
|
51
51
|
Enabled: false
|
52
52
|
|
53
53
|
Layout/EndOfLine:
|
54
54
|
EnforcedStyle: lf
|
55
|
+
|
56
|
+
Lint/ConstantDefinitionInBlock:
|
57
|
+
Exclude:
|
58
|
+
- 'spec/**/*.rb'
|
59
|
+
|
60
|
+
Lint/EmptyClass:
|
61
|
+
Exclude:
|
62
|
+
- 'spec/**/*.rb'
|
63
|
+
|
64
|
+
# Defaults after the Rubocop upgrade
|
65
|
+
Gemspec/DateAssignment: # new in 1.10
|
66
|
+
Enabled: true
|
67
|
+
Layout/LineEndStringConcatenationIndentation: # new in 1.18
|
68
|
+
Enabled: true
|
69
|
+
Layout/SpaceBeforeBrackets: # new in 1.7
|
70
|
+
Enabled: true
|
71
|
+
Lint/AmbiguousAssignment: # new in 1.7
|
72
|
+
Enabled: true
|
73
|
+
Lint/AmbiguousOperatorPrecedence: # new in 1.21
|
74
|
+
Enabled: true
|
75
|
+
Lint/AmbiguousRange: # new in 1.19
|
76
|
+
Enabled: true
|
77
|
+
Lint/DeprecatedConstants: # new in 1.8
|
78
|
+
Enabled: true
|
79
|
+
Lint/DuplicateBranch: # new in 1.3
|
80
|
+
Enabled: true
|
81
|
+
Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
|
82
|
+
Enabled: true
|
83
|
+
Lint/EmptyBlock: # new in 1.1
|
84
|
+
Enabled: true
|
85
|
+
Lint/EmptyClass: # new in 1.3
|
86
|
+
Enabled: true
|
87
|
+
Lint/EmptyInPattern: # new in 1.16
|
88
|
+
Enabled: true
|
89
|
+
Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
|
90
|
+
Enabled: true
|
91
|
+
Lint/LambdaWithoutLiteralBlock: # new in 1.8
|
92
|
+
Enabled: true
|
93
|
+
Lint/NoReturnInBeginEndBlocks: # new in 1.2
|
94
|
+
Enabled: true
|
95
|
+
Lint/NumberedParameterAssignment: # new in 1.9
|
96
|
+
Enabled: true
|
97
|
+
Lint/OrAssignmentToConstant: # new in 1.9
|
98
|
+
Enabled: true
|
99
|
+
Lint/RedundantDirGlobSort: # new in 1.8
|
100
|
+
Enabled: true
|
101
|
+
Lint/SymbolConversion: # new in 1.9
|
102
|
+
Enabled: true
|
103
|
+
Lint/ToEnumArguments: # new in 1.1
|
104
|
+
Enabled: true
|
105
|
+
Lint/TripleQuotes: # new in 1.9
|
106
|
+
Enabled: true
|
107
|
+
Lint/UnexpectedBlockArity: # new in 1.5
|
108
|
+
Enabled: true
|
109
|
+
Lint/UnmodifiedReduceAccumulator: # new in 1.1
|
110
|
+
Enabled: true
|
111
|
+
Style/ArgumentsForwarding: # new in 1.1
|
112
|
+
Enabled: true
|
113
|
+
Style/CollectionCompact: # new in 1.2
|
114
|
+
Enabled: true
|
115
|
+
Style/DocumentDynamicEvalDefinition: # new in 1.1
|
116
|
+
Enabled: true
|
117
|
+
Style/EndlessMethod: # new in 1.8
|
118
|
+
Enabled: true
|
119
|
+
Style/HashConversion: # new in 1.10
|
120
|
+
Enabled: true
|
121
|
+
Style/HashExcept: # new in 1.7
|
122
|
+
Enabled: true
|
123
|
+
Style/IfWithBooleanLiteralBranches: # new in 1.9
|
124
|
+
Enabled: true
|
125
|
+
Style/InPatternThen: # new in 1.16
|
126
|
+
Enabled: true
|
127
|
+
Style/MultilineInPatternThen: # new in 1.16
|
128
|
+
Enabled: true
|
129
|
+
Style/NegatedIfElseCondition: # new in 1.2
|
130
|
+
Enabled: true
|
131
|
+
Style/NilLambda: # new in 1.3
|
132
|
+
Enabled: true
|
133
|
+
Style/QuotedSymbols: # new in 1.16
|
134
|
+
Enabled: true
|
135
|
+
Style/RedundantArgument: # new in 1.4
|
136
|
+
Enabled: true
|
137
|
+
Style/RedundantSelfAssignmentBranch: # new in 1.19
|
138
|
+
Enabled: true
|
139
|
+
Style/StringChars: # new in 1.12
|
140
|
+
Enabled: true
|
141
|
+
Style/SwapValues: # new in 1.1
|
142
|
+
Enabled: true
|
143
|
+
Gemspec/RequireMFA: # new in 1.23
|
144
|
+
Enabled: true
|
145
|
+
Lint/RequireRelativeSelfPath: # new in 1.22
|
146
|
+
Enabled: true
|
147
|
+
Lint/UselessRuby2Keywords: # new in 1.23
|
148
|
+
Enabled: true
|
149
|
+
Naming/BlockForwarding: # new in 1.24
|
150
|
+
Enabled: true
|
151
|
+
Security/IoMethods: # new in 1.22
|
152
|
+
Enabled: true
|
153
|
+
Style/FileRead: # new in 1.24
|
154
|
+
Enabled: true
|
155
|
+
Style/FileWrite: # new in 1.24
|
156
|
+
Enabled: true
|
157
|
+
Style/MapToHash: # new in 1.24
|
158
|
+
Enabled: true
|
159
|
+
Style/NestedFileDirname: # new in 1.26
|
160
|
+
Enabled: true
|
161
|
+
Style/NumberedParameters: # new in 1.22
|
162
|
+
Enabled: true
|
163
|
+
Style/NumberedParametersLimit: # new in 1.22
|
164
|
+
Enabled: true
|
165
|
+
Style/OpenStructUse: # new in 1.23
|
166
|
+
Enabled: true
|
167
|
+
Style/SelectByRegexp: # new in 1.22
|
168
|
+
Enabled: true
|
data/.travis.yml
CHANGED
@@ -4,10 +4,9 @@ env:
|
|
4
4
|
- RUN_COVERAGE_REPORT=true
|
5
5
|
|
6
6
|
rvm:
|
7
|
-
- 2.4.2
|
8
7
|
- 2.5.3
|
9
|
-
- 2.6.
|
10
|
-
- 2.7.
|
8
|
+
- 2.6.6
|
9
|
+
- 2.7.2
|
11
10
|
|
12
11
|
before_install:
|
13
12
|
- 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
|
@@ -26,9 +25,5 @@ gemfile:
|
|
26
25
|
|
27
26
|
matrix:
|
28
27
|
exclude:
|
29
|
-
- rvm: 2.
|
30
|
-
gemfile: gemfiles/activesupport_6.gemfile
|
31
|
-
- rvm: 2.7.0
|
32
|
-
gemfile: gemfiles/activesupport_3.gemfile
|
33
|
-
- rvm: 2.7.0
|
28
|
+
- rvm: 2.7.2
|
34
29
|
gemfile: gemfiles/activesupport_4.gemfile
|
data/README.md
CHANGED
@@ -1,20 +1,15 @@
|
|
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
|
-
[![
|
4
|
+
[![CI Tests](https://github.com/adomokos/light-service/actions/workflows/project-build.yml/badge.svg)](https://github.com/adomokos/light-service/actions/workflows/project-build.yml)
|
5
5
|
[![codecov](https://codecov.io/gh/adomokos/light-service/branch/master/graph/badge.svg)](https://codecov.io/gh/adomokos/light-service)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/adomokos/light-service.svg)](https://codeclimate.com/github/adomokos/light-service)
|
7
7
|
[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
|
8
8
|
[![Download Count](https://ruby-gem-downloads-badge.herokuapp.com/light-service?type=total)](https://rubygems.org/gems/light-service)
|
9
9
|
|
10
|
-
|
10
|
+
LightService is a powerful and flexible service skeleton framework with an emphasis on simplicity
|
11
11
|
|
12
|
-
|
13
|
-
<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.
|
14
|
-
|
15
|
-
<br>
|
16
|
-
|
17
|
-
## Table of Content
|
12
|
+
## Table of Contents
|
18
13
|
* [Why LightService?](#why-lightservice)
|
19
14
|
* [Getting Started](#getting-started)
|
20
15
|
* [Requirements](#requirements)
|
@@ -26,14 +21,18 @@
|
|
26
21
|
* [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
|
27
22
|
* [Benchmarking Actions with Around Advice](#benchmarking-actions-with-around-advice)
|
28
23
|
* [Before and After Action Hooks](#before-and-after-action-hooks)
|
24
|
+
* [Expects and Promises](#expects-and-promises)
|
25
|
+
* [Default values for optional Expected keys](#default-values-for-optional-expected-keys)
|
29
26
|
* [Key Aliases](#key-aliases)
|
30
27
|
* [Logging](#logging)
|
31
28
|
* [Error Codes](#error-codes)
|
32
29
|
* [Action Rollback](#action-rollback)
|
33
30
|
* [Localizing Messages](#localizing-messages)
|
34
|
-
* [
|
31
|
+
* [Orchestrating Logic in Organizers](#orchestrating-logic-in-organizers)
|
35
32
|
* [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
|
36
33
|
* [Rails support](#rails-support)
|
34
|
+
* [Implementations in other languages](#other-implementations)
|
35
|
+
* [Contributing](#contributing)
|
37
36
|
|
38
37
|
## Why LightService?
|
39
38
|
|
@@ -532,9 +531,11 @@ These ideas are originally from Aspect Oriented Programming, read more about the
|
|
532
531
|
## Expects and Promises
|
533
532
|
The `expects` and `promises` macros are rules for the inputs/outputs of an action.
|
534
533
|
`expects` describes what keys it needs to execute, and `promises` makes sure the keys are in the context after the
|
535
|
-
action is reduced. If either of them are violated, a
|
534
|
+
action is reduced. If either of them are violated, a `LightService::ExpectedKeysNotInContextError` or
|
535
|
+
`LightService::PromisedKeysNotInContextError` exception respectively will be thrown.
|
536
536
|
|
537
537
|
This is how it's used:
|
538
|
+
|
538
539
|
```ruby
|
539
540
|
class FooAction
|
540
541
|
extend LightService::Action
|
@@ -542,46 +543,78 @@ class FooAction
|
|
542
543
|
promises :bar
|
543
544
|
|
544
545
|
executed do |context|
|
545
|
-
|
546
|
-
|
547
|
-
bar = baz + 2
|
548
|
-
context[:bar] = bar
|
546
|
+
context.bar = context.baz + 2
|
549
547
|
end
|
550
548
|
end
|
551
549
|
```
|
552
550
|
|
553
|
-
The `expects` macro
|
554
|
-
makes it available to you through a reader.
|
551
|
+
The `expects` macro will pull the value with the expected key from the context, and
|
552
|
+
makes it available to you through a reader.
|
553
|
+
|
554
|
+
The `promises` macro will not only check if the context has the promised keys, it
|
555
|
+
also sets them for you in the context if you use the accessor with the same name,
|
556
|
+
much the same way as the expects macro works.
|
557
|
+
|
558
|
+
The context object is essentially a smarter-than-normal Hash. Take a look at [this spec](spec/action_expects_and_promises_spec.rb)
|
559
|
+
to see expects and promises used with and without accessors.
|
560
|
+
|
561
|
+
### Default values for optional Expected keys
|
562
|
+
|
563
|
+
When you have an expected key that has a sensible default which should be used everywhere and
|
564
|
+
only overridden on an as-needed basis, you can specify a default value. An example use-case
|
565
|
+
is a flag that allows a failure from a service under most circumstances to avoid failing an
|
566
|
+
entire workflow because of a non-critical action.
|
567
|
+
|
568
|
+
LightService provides two mechanisms for specifying default values:
|
569
|
+
|
570
|
+
1. A static value that is used as-is
|
571
|
+
2. A callable that takes the current context as a param
|
572
|
+
|
573
|
+
Using the above use case, consider an action that sends a text message. In most cases,
|
574
|
+
if there is a problem sending the text message, it might be OK for it to fail. We will
|
575
|
+
`expect` an `allow_failure` key, but set it with a default, like so:
|
555
576
|
|
556
577
|
```ruby
|
557
|
-
class
|
578
|
+
class SendSMS
|
558
579
|
extend LightService::Action
|
559
|
-
expects :
|
560
|
-
|
580
|
+
expects :message, :user
|
581
|
+
expects :allow_failure, default: true
|
561
582
|
|
562
583
|
executed do |context|
|
563
|
-
|
564
|
-
|
584
|
+
sms_api = SMSService.new(key: ENV["SMS_API_KEY"])
|
585
|
+
status = sms_api.send(ctx.user.mobile_number, ctx.message)
|
586
|
+
|
587
|
+
if !status.sent_ok?
|
588
|
+
ctx.fail!(status.err_msg) unless ctx.allow_failure
|
589
|
+
end
|
565
590
|
end
|
566
591
|
end
|
567
592
|
```
|
568
593
|
|
569
|
-
|
570
|
-
|
594
|
+
Default values can also be processed dynamically by providing a callable. Any values already
|
595
|
+
specified in the context are available to it via Hash key lookup syntax. e.g.
|
571
596
|
|
572
597
|
```ruby
|
573
|
-
class
|
598
|
+
class SendSMS
|
574
599
|
extend LightService::Action
|
575
|
-
expects :
|
576
|
-
|
600
|
+
expects :message, :user
|
601
|
+
expects :allow_failure, default: ->(ctx) { !ctx[:user].admin? } # Admins must always get SMS'
|
577
602
|
|
578
603
|
executed do |context|
|
579
|
-
|
604
|
+
sms_api = SMSService.new(key: ENV["SMS_API_KEY"])
|
605
|
+
status = sms_api.send(ctx.user.mobile_number, ctx.message)
|
606
|
+
|
607
|
+
if !status.sent_ok?
|
608
|
+
ctx.fail!(status.err_msg) unless ctx.allow_failure
|
609
|
+
end
|
580
610
|
end
|
581
611
|
end
|
582
612
|
```
|
583
613
|
|
584
|
-
|
614
|
+
**Note** that default values must be specified one at a time on their own line.
|
615
|
+
|
616
|
+
You can then call an action or organizer that uses an action with defaults without specifying
|
617
|
+
the expected key that has a default.
|
585
618
|
|
586
619
|
## Key Aliases
|
587
620
|
The `aliases` macro sets up pairs of keys and aliases in an organizer. Actions can access the context using the aliases.
|
@@ -866,9 +899,13 @@ end
|
|
866
899
|
|
867
900
|
To get the value of a `fail!` or `succeed!` message, simply call `#message` on the returned context.
|
868
901
|
|
869
|
-
##
|
902
|
+
## Orchestrating Logic in Organizers
|
870
903
|
|
871
|
-
The Organizer - Action combination works really well for simple use cases. However, as business logic gets more complex, or when LightService is used in an ETL workflow, the code that routes the different organizers becomes very complex and imperative.
|
904
|
+
The Organizer - Action combination works really well for simple use cases. However, as business logic gets more complex, or when LightService is used in an ETL workflow, the code that routes the different organizers becomes very complex and imperative.
|
905
|
+
|
906
|
+
In the past, this was solved using Orchestrators. As of [Version 0.9.0 Orchestrators have been deprecated](https://github.com/adomokos/light-service/pull/132). All their functionality is now usable directly within Organizers. Read on to understand how to orchestrate workflows from within a single Organizer.
|
907
|
+
|
908
|
+
Let's look at a piece of code that does basic data transformations:
|
872
909
|
|
873
910
|
```ruby
|
874
911
|
class ExtractsTransformsLoadsData
|
@@ -922,27 +959,33 @@ end
|
|
922
959
|
|
923
960
|
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.
|
924
961
|
|
925
|
-
The
|
962
|
+
The 9 different orchestrator constructs an organizer can have:
|
926
963
|
|
927
964
|
1. `reduce_until`
|
928
965
|
2. `reduce_if`
|
929
|
-
3. `
|
930
|
-
4. `
|
931
|
-
5. `
|
932
|
-
6. `
|
933
|
-
7. `
|
966
|
+
3. `reduce_if_else`
|
967
|
+
4. `reduce_case`
|
968
|
+
5. `iterate`
|
969
|
+
6. `execute`
|
970
|
+
7. `with_callback`
|
971
|
+
8. `add_to_context`
|
972
|
+
9. `add_aliases`
|
934
973
|
|
935
974
|
`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.
|
936
975
|
|
937
976
|
`reduce_if` will reduce the included organizers and/or actions if the predicate in the lambda evaluates to true. [This acceptance test](spec/acceptance/organizer/reduce_if_spec.rb) describes this functionality.
|
938
977
|
|
978
|
+
`reduce_if_else` takes three arguments, a condition lambda, a first set of "if true" steps, and a second set of "if false" steps. If the lambda evaluates to true, the "if true" steps are executed, otherwise the "else steps" are executed. [This acceptance test](spec/acceptance/organizer/reduce_if_else_spec.rb) describes this functionality.
|
979
|
+
|
980
|
+
`reduce_case` behaves like a Ruby `case` statement. The first parameter `value` is the key of the value within the context that will be worked with. The second parameter `when` is a hash where the keys are conditional values and the values are steps to take if the condition matches. The final parameter `else` is a set of steps to take if no conditions within the `when` parameter are met. [This acceptance test](spec/acceptance/organizer/reduce_case_spec.rb) describes this functionality.
|
981
|
+
|
939
982
|
`iterate` gives your iteration logic, the symbol you define there has to be in the context as a key. For example, to iterate over items you will use `iterate(:items)` in your steps, the context needs to have `items` as a key, otherwise it will fail. The organizer will singularize the collection name and will put the actual item into the context under that name. Remaining with the example above, each element will be accessible by the name `item` for the actions in the `iterate` steps. [This acceptance test](spec/acceptance/organizer/iterate_spec.rb) should provide you with an example.
|
940
983
|
|
941
984
|
To take advantage of another organizer or action, you might need to tweak the context a bit. Let's say you have a hash, and you need to iterate over its values in a series of action. To alter the context and have the values assigned into a variable, you need to create a new action with 1 line of code in it. That seems a lot of ceremony for a simple change. You can do that in a `execute` method like this `execute(->(ctx) { ctx[:some_values] = ctx.some_hash.values })`. [This test](spec/acceptance/organizer/execute_spec.rb) describes how you can use it.
|
942
985
|
|
943
986
|
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.
|
944
987
|
|
945
|
-
`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.
|
988
|
+
`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. Keys are also made available as accessors on the context object and can be used just like methods exposed via `expects` and `promises`. [This test](spec/acceptance/organizer/add_to_context_spec.rb) describes its functionality.
|
946
989
|
|
947
990
|
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.
|
948
991
|
|
@@ -1046,6 +1089,15 @@ them through the context. A stub context will be created in the test file using
|
|
1046
1089
|
|
1047
1090
|
When specifying `promises`, specs will be created testing for their existence after executing the action.
|
1048
1091
|
|
1092
|
+
## Other implementations
|
1093
|
+
|
1094
|
+
| Language | Repo | Author |
|
1095
|
+
| :--------- |:------------------------------------------------------------------------| :------------------------------------------------------|
|
1096
|
+
| Python | [pyservice](https://github.com/adomokos/pyservice) | [@adomokos](https://github.com/adomokos) |
|
1097
|
+
| PHP | [light-service](https://github.com/douglasgreyling/light-service) | [@douglasgreyling](https://github.com/douglasgreyling) |
|
1098
|
+
| JavaScript | [light-service.js](https://github.com/douglasgreyling/light-service.js) | [@douglasgreyling](https://github.com/douglasgreyling) |
|
1099
|
+
|
1100
|
+
|
1049
1101
|
## Contributing
|
1050
1102
|
1. Fork it
|
1051
1103
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
data/RELEASES.md
CHANGED
@@ -1,11 +1,28 @@
|
|
1
1
|
A brief list of new features and changes introduced with the specified version.
|
2
2
|
|
3
|
+
### 0.18.0
|
4
|
+
* [Remove Ruby 2.6, add 3.1 to build](https://github.com/adomokos/light-service/pull/233)
|
5
|
+
* [Add reduce_when](https://github.com/adomokos/light-service/pull/232)
|
6
|
+
* [Drop Ruby 2.5 version support, add 3.0 build](https://github.com/adomokos/light-service/pull/225)
|
7
|
+
* [Support for named argument in Ruby](https://github.com/adomokos/light-service/pull/224)
|
8
|
+
|
9
|
+
### 0.17.0
|
10
|
+
* [Fix around_action hook for nested actions](https://github.com/adomokos/light-service/pull/217)
|
11
|
+
* [Add ReduceIfElse macro](https://github.com/adomokos/light-service/pull/218)
|
12
|
+
* [Implement support for default values for optional expected keys](https://github.com/adomokos/light-service/pull/219)
|
13
|
+
* [Add light-service.js implementation to README](https://github.com/adomokos/light-service/pull/222)
|
14
|
+
|
15
|
+
### 0.16.0
|
16
|
+
* [Drop Ruby 2.4 support](https://github.com/adomokos/light-service/pull/207)
|
17
|
+
* [Fix callback current action](https://github.com/adomokos/light-service/pull/209)
|
18
|
+
* [Add Context accessors](https://github.com/adomokos/light-service/pull/211)
|
19
|
+
* [Switched to GH Actions from Travis CI](https://github.com/adomokos/light-service/pull/212)
|
20
|
+
|
3
21
|
### 0.15.0
|
4
22
|
* [Add Rails Generators](https://github.com/adomokos/light-service/pull/194) - LightService actions and organizers can be generated with generators
|
5
23
|
* [Add CodeCov](https://github.com/adomokos/light-service/pull/195) - Upload code coverage report to codecov.io
|
6
24
|
* [Remove ActiveSupport 3 checks](https://github.com/adomokos/light-service/pull/197) - They are unsupported, no need to tests them any more.
|
7
25
|
|
8
|
-
|
9
26
|
### 0.14.0
|
10
27
|
* [Add 'organized_by' to context](https://github.com/adomokos/light-service/pull/192) - Context now have an #organized_by attribute
|
11
28
|
|
@@ -25,7 +25,6 @@ module LightService
|
|
25
25
|
options.tests? && test_framework_supported?
|
26
26
|
end
|
27
27
|
|
28
|
-
# rubocop:disable Metrics/AbcSize
|
29
28
|
def create_required_gen_vals_from(name)
|
30
29
|
path_parts = name.underscore.split('/')
|
31
30
|
|
@@ -39,7 +38,6 @@ module LightService
|
|
39
38
|
:full_class_name => name.classify
|
40
39
|
}
|
41
40
|
end
|
42
|
-
# rubocop:enable Metrics/AbcSize
|
43
41
|
end
|
44
42
|
end
|
45
43
|
end
|
data/lib/light-service/action.rb
CHANGED
@@ -15,6 +15,12 @@ module LightService
|
|
15
15
|
|
16
16
|
module Macros
|
17
17
|
def expects(*args)
|
18
|
+
if expect_key_having_default?(args)
|
19
|
+
available_defaults[args.first] = args.last[:default]
|
20
|
+
|
21
|
+
args = [args.first]
|
22
|
+
end
|
23
|
+
|
18
24
|
expected_keys.concat(args)
|
19
25
|
end
|
20
26
|
|
@@ -30,8 +36,8 @@ module LightService
|
|
30
36
|
@promised_keys ||= []
|
31
37
|
end
|
32
38
|
|
33
|
-
def executed
|
34
|
-
define_singleton_method :execute do |context =
|
39
|
+
def executed(*_args, &block)
|
40
|
+
define_singleton_method :execute do |context = Context.make|
|
35
41
|
action_context = create_action_context(context)
|
36
42
|
return action_context if action_context.stop_processing?
|
37
43
|
|
@@ -43,7 +49,11 @@ module LightService
|
|
43
49
|
|
44
50
|
catch(:jump_when_failed) do
|
45
51
|
call_before_action(action_context)
|
46
|
-
|
52
|
+
|
53
|
+
execute_action(action_context, &block)
|
54
|
+
|
55
|
+
# Reset the stored action in case it was changed downstream
|
56
|
+
action_context.current_action = self
|
47
57
|
call_after_action(action_context)
|
48
58
|
end
|
49
59
|
end
|
@@ -63,8 +73,34 @@ module LightService
|
|
63
73
|
|
64
74
|
private
|
65
75
|
|
76
|
+
def execute_action(context)
|
77
|
+
if around_action_context?(context)
|
78
|
+
context.around_actions.call(context) do
|
79
|
+
yield(context)
|
80
|
+
context
|
81
|
+
end
|
82
|
+
else
|
83
|
+
yield(context)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def available_defaults
|
88
|
+
@available_defaults ||= {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def expect_key_having_default?(key)
|
92
|
+
return false unless key.size == 2 && key.last.is_a?(Hash)
|
93
|
+
return true if key.last.key?(:default)
|
94
|
+
|
95
|
+
bad_key = key.last.keys.first
|
96
|
+
err_msg = "Specify defaults with a `default` key. You have #{bad_key}."
|
97
|
+
raise UnusableExpectKeyDefaultError, err_msg
|
98
|
+
end
|
99
|
+
|
66
100
|
def create_action_context(context)
|
67
|
-
|
101
|
+
usable_defaults(context).each do |ctx_key, default|
|
102
|
+
context[ctx_key] = extract_default(default, context)
|
103
|
+
end
|
68
104
|
|
69
105
|
LightService::Context.make(context)
|
70
106
|
end
|
@@ -73,6 +109,22 @@ module LightService
|
|
73
109
|
expected_keys + promised_keys
|
74
110
|
end
|
75
111
|
|
112
|
+
def missing_expected_keys(context)
|
113
|
+
expected_keys - context.keys
|
114
|
+
end
|
115
|
+
|
116
|
+
def usable_defaults(context)
|
117
|
+
available_defaults.slice(
|
118
|
+
*(missing_expected_keys(context) & available_defaults.keys)
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
def extract_default(default, context)
|
123
|
+
return default unless default.respond_to?(:call)
|
124
|
+
|
125
|
+
default.call(context)
|
126
|
+
end
|
127
|
+
|
76
128
|
def call_before_action(context)
|
77
129
|
invoke_callbacks(context[:_before_actions], context)
|
78
130
|
end
|
@@ -90,6 +142,11 @@ module LightService
|
|
90
142
|
|
91
143
|
context
|
92
144
|
end
|
145
|
+
|
146
|
+
def around_action_context?(context)
|
147
|
+
context.instance_of?(Context) &&
|
148
|
+
context.around_actions.respond_to?(:call)
|
149
|
+
end
|
93
150
|
end
|
94
151
|
end
|
95
152
|
end
|
@@ -24,7 +24,7 @@ module LightService
|
|
24
24
|
|
25
25
|
def error_message
|
26
26
|
"#{type_name} #{format_keys(keys_not_found(keys))} " \
|
27
|
-
|
27
|
+
"to be in the context during #{action}"
|
28
28
|
end
|
29
29
|
|
30
30
|
def throw_error_predicate(_keys)
|
@@ -95,7 +95,7 @@ module LightService
|
|
95
95
|
|
96
96
|
def error_message
|
97
97
|
"promised or expected keys cannot be a " \
|
98
|
-
|
98
|
+
"reserved key: [#{format_keys(violated_keys)}]"
|
99
99
|
end
|
100
100
|
|
101
101
|
def keys
|
@@ -111,7 +111,26 @@ module LightService
|
|
111
111
|
end
|
112
112
|
|
113
113
|
def reserved_keys
|
114
|
-
%i[message error_code current_action].freeze
|
114
|
+
%i[message error_code current_action organized_by].freeze
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class ReservedKeysViaOrganizerVerifier < ReservedKeysVerifier
|
119
|
+
# rubocop:disable Lint/MissingSuper
|
120
|
+
def initialize(context_data)
|
121
|
+
@context = LightService::Context.make(context_data)
|
122
|
+
end
|
123
|
+
# rubocop:enable Lint/MissingSuper
|
124
|
+
|
125
|
+
def violated_keys
|
126
|
+
context.keys.map(&:to_sym) & reserved_keys
|
127
|
+
end
|
128
|
+
|
129
|
+
def error_message
|
130
|
+
<<~ERR
|
131
|
+
reserved keys cannot be added to the context
|
132
|
+
reserved key: [#{format_keys(violated_keys)}]
|
133
|
+
ERR
|
115
134
|
end
|
116
135
|
end
|
117
136
|
end
|