decouplio 1.0.0alpha
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 +7 -0
- data/.circleci/config.yml +53 -0
- data/.dockerignore +12 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +106 -0
- data/.rubocop_todo.yml +147 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +3 -0
- data/Dockerfile +12 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +29 -0
- data/Rakefile +8 -0
- data/benchmarks/.ruby-version +1 -0
- data/benchmarks/Dockerfile +12 -0
- data/benchmarks/Gemfile +11 -0
- data/benchmarks/benchmarks.rb +527 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/decouplio.gemspec +40 -0
- data/docker-compose.yml +18 -0
- data/docs/_config.yml +1 -0
- data/docs/benchmarks.md +1 -0
- data/docs/context.md +74 -0
- data/docs/context.rb +62 -0
- data/docs/doby.md +80 -0
- data/docs/doby.rb +38 -0
- data/docs/error_store.md +347 -0
- data/docs/error_store.rb +202 -0
- data/docs/fail.md +1016 -0
- data/docs/fail.rb +762 -0
- data/docs/index.md +25 -0
- data/docs/inner_action.md +63 -0
- data/docs/inner_action.rb +43 -0
- data/docs/logic_block.md +25 -0
- data/docs/octo.md +269 -0
- data/docs/octo.rb +164 -0
- data/docs/pass.md +309 -0
- data/docs/pass.rb +213 -0
- data/docs/quick_start.md +71 -0
- data/docs/quick_start.rb +38 -0
- data/docs/resq.md +263 -0
- data/docs/resq.rb +176 -0
- data/docs/step.md +737 -0
- data/docs/step.rb +526 -0
- data/docs/step_as_a_service.md +109 -0
- data/docs/step_as_a_service.rb +77 -0
- data/docs/wrap.md +232 -0
- data/docs/wrap.rb +137 -0
- data/lib/decouplio/action.rb +111 -0
- data/lib/decouplio/composer.rb +599 -0
- data/lib/decouplio/const/colors.rb +25 -0
- data/lib/decouplio/const/reserved_methods.rb +18 -0
- data/lib/decouplio/const/results.rb +13 -0
- data/lib/decouplio/const/types.rb +173 -0
- data/lib/decouplio/const/validations/action_option_class.rb +17 -0
- data/lib/decouplio/const/validations/common.rb +14 -0
- data/lib/decouplio/const/validations/fail.rb +34 -0
- data/lib/decouplio/const/validations/logic.rb +15 -0
- data/lib/decouplio/const/validations/octo.rb +44 -0
- data/lib/decouplio/const/validations/palp.rb +20 -0
- data/lib/decouplio/const/validations/pass.rb +32 -0
- data/lib/decouplio/const/validations/resq.rb +67 -0
- data/lib/decouplio/const/validations/step.rb +35 -0
- data/lib/decouplio/const/validations/wrap.rb +41 -0
- data/lib/decouplio/default_error_handler.rb +24 -0
- data/lib/decouplio/errors/action_class_error.rb +26 -0
- data/lib/decouplio/errors/base_error.rb +30 -0
- data/lib/decouplio/errors/error_store_error.rb +17 -0
- data/lib/decouplio/errors/extra_key_for_fail_error.rb +26 -0
- data/lib/decouplio/errors/extra_key_for_octo_error.rb +26 -0
- data/lib/decouplio/errors/extra_key_for_pass_error.rb +26 -0
- data/lib/decouplio/errors/extra_key_for_resq_error.rb +29 -0
- data/lib/decouplio/errors/extra_key_for_step_error.rb +23 -0
- data/lib/decouplio/errors/extra_key_for_wrap_error.rb +23 -0
- data/lib/decouplio/errors/fail_controversial_keys_error.rb +26 -0
- data/lib/decouplio/errors/fail_finish_him_error.rb +26 -0
- data/lib/decouplio/errors/fail_is_first_step_error.rb +18 -0
- data/lib/decouplio/errors/invalid_error_class_error.rb +29 -0
- data/lib/decouplio/errors/invalid_wrap_name_error.rb +23 -0
- data/lib/decouplio/errors/logic_is_not_defined_error.rb +23 -0
- data/lib/decouplio/errors/logic_redefinition_error.rb +23 -0
- data/lib/decouplio/errors/octo_block_is_not_defined_error.rb +18 -0
- data/lib/decouplio/errors/octo_controversial_keys_error.rb +26 -0
- data/lib/decouplio/errors/options_validation_error.rb +7 -0
- data/lib/decouplio/errors/palp_block_is_not_defined_error.rb +18 -0
- data/lib/decouplio/errors/palp_is_not_defined_error.rb +26 -0
- data/lib/decouplio/errors/palp_validation_error.rb +21 -0
- data/lib/decouplio/errors/pass_controversial_keys_error.rb +26 -0
- data/lib/decouplio/errors/pass_finish_him_error.rb +26 -0
- data/lib/decouplio/errors/required_options_is_missing_for_octo_error.rb +25 -0
- data/lib/decouplio/errors/resq_definition_error.rb +21 -0
- data/lib/decouplio/errors/resq_error_class_error.rb +29 -0
- data/lib/decouplio/errors/resq_handler_method_error.rb +26 -0
- data/lib/decouplio/errors/step_controversial_keys_error.rb +26 -0
- data/lib/decouplio/errors/step_finish_him_error.rb +26 -0
- data/lib/decouplio/errors/step_is_not_defined_for_fail_error.rb +26 -0
- data/lib/decouplio/errors/step_is_not_defined_for_step_error.rb +27 -0
- data/lib/decouplio/errors/step_is_not_defined_for_wrap_error.rb +26 -0
- data/lib/decouplio/errors/step_name_error.rb +20 -0
- data/lib/decouplio/errors/wrap_block_is_not_defined_error.rb +18 -0
- data/lib/decouplio/errors/wrap_controversial_keys_error.rb +26 -0
- data/lib/decouplio/errors/wrap_finish_him_error.rb +26 -0
- data/lib/decouplio/errors/wrap_klass_method_error.rb +23 -0
- data/lib/decouplio/flow.rb +17 -0
- data/lib/decouplio/logic_dsl.rb +96 -0
- data/lib/decouplio/octo_hash_case.rb +31 -0
- data/lib/decouplio/octo_options_validator.rb +89 -0
- data/lib/decouplio/options_validator.rb +566 -0
- data/lib/decouplio/processor.rb +20 -0
- data/lib/decouplio/steps/base_resq.rb +31 -0
- data/lib/decouplio/steps/base_step.rb +16 -0
- data/lib/decouplio/steps/doby.rb +32 -0
- data/lib/decouplio/steps/fail.rb +50 -0
- data/lib/decouplio/steps/if_condition_fail.rb +28 -0
- data/lib/decouplio/steps/if_condition_pass.rb +26 -0
- data/lib/decouplio/steps/inner_action_fail.rb +55 -0
- data/lib/decouplio/steps/inner_action_pass.rb +38 -0
- data/lib/decouplio/steps/inner_action_step.rb +51 -0
- data/lib/decouplio/steps/octo.rb +22 -0
- data/lib/decouplio/steps/pass.rb +35 -0
- data/lib/decouplio/steps/resq_fail.rb +10 -0
- data/lib/decouplio/steps/resq_pass.rb +10 -0
- data/lib/decouplio/steps/service_fail.rb +51 -0
- data/lib/decouplio/steps/service_pass.rb +36 -0
- data/lib/decouplio/steps/service_step.rb +47 -0
- data/lib/decouplio/steps/step.rb +43 -0
- data/lib/decouplio/steps/unless_condition_fail.rb +28 -0
- data/lib/decouplio/steps/unless_condition_pass.rb +26 -0
- data/lib/decouplio/steps/wrap.rb +62 -0
- data/lib/decouplio/validators/condition.rb +49 -0
- data/lib/decouplio/version.rb +5 -0
- data/lib/decouplio.rb +7 -0
- metadata +266 -0
data/docs/context.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative '../lib/decouplio'
|
2
|
+
|
3
|
+
class SomeAction < Decouplio::Action
|
4
|
+
logic do
|
5
|
+
step :step_one
|
6
|
+
step :step_two
|
7
|
+
end
|
8
|
+
|
9
|
+
def step_one(**)
|
10
|
+
ctx[:step_one] = 'Step one ctx value'
|
11
|
+
end
|
12
|
+
|
13
|
+
# step method receives ctx as an argument
|
14
|
+
def step_two(step_one:, **)
|
15
|
+
ctx[:step_two] = step_one
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
action = SomeAction.call
|
20
|
+
|
21
|
+
puts action # =>
|
22
|
+
# Result: success
|
23
|
+
|
24
|
+
# Railway Flow:
|
25
|
+
# step_one -> step_two
|
26
|
+
|
27
|
+
# Context:
|
28
|
+
# {:step_one=>"Step one ctx value", :step_two=>"Step one ctx value"}
|
29
|
+
|
30
|
+
# Errors:
|
31
|
+
# {}
|
32
|
+
|
33
|
+
|
34
|
+
class SomeActionCtx < Decouplio::Action
|
35
|
+
logic do
|
36
|
+
step :step_one
|
37
|
+
end
|
38
|
+
|
39
|
+
def step_one(**)
|
40
|
+
puts ctx
|
41
|
+
ctx[:result] = ctx[:one] + ctx[:two]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
action = SomeActionCtx.call(
|
46
|
+
one: 1,
|
47
|
+
two: 2
|
48
|
+
)
|
49
|
+
|
50
|
+
action[:result] # => 3
|
51
|
+
|
52
|
+
puts action # =>
|
53
|
+
# Result: success
|
54
|
+
|
55
|
+
# Railway Flow:
|
56
|
+
# step_one
|
57
|
+
|
58
|
+
# Context:
|
59
|
+
# {:one=>1, :two=>2, :result=>3}
|
60
|
+
|
61
|
+
# Errors:
|
62
|
+
# {}
|
data/docs/doby.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Doby
|
2
|
+
|
3
|
+
It's a step type to make configurable manipulations with action context.
|
4
|
+
|
5
|
+
|
6
|
+
## Signature
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
doby(class_constant, **options)
|
10
|
+
```
|
11
|
+
|
12
|
+
## How to use?
|
13
|
+
|
14
|
+
Create the ruby class which has `.call` class method.
|
15
|
+
|
16
|
+
`.call` method signature:
|
17
|
+
```ruby
|
18
|
+
# :ctx - is the required kwarg
|
19
|
+
# ** - kwargs passed from action
|
20
|
+
def self.call(ctx:, **)
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class AssignDoby
|
26
|
+
def self.call(ctx:, to:, from: nil, value: nil)
|
27
|
+
raise 'from/value is empty' unless from || value
|
28
|
+
|
29
|
+
ctx[to] = value || ctx[from]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Now you can use `AssignDoby` class inside action
|
35
|
+
```ruby
|
36
|
+
require 'decouplio'
|
37
|
+
|
38
|
+
class AssignDoby
|
39
|
+
def self.call(ctx:, to:, from: nil, value: nil)
|
40
|
+
raise 'from/value is empty' unless from || value
|
41
|
+
|
42
|
+
ctx[to] = value || ctx[from]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class SomeAction < Decouplio::Action
|
47
|
+
logic do
|
48
|
+
step :user
|
49
|
+
step AssignDoby, to: :current_user, from: :user
|
50
|
+
end
|
51
|
+
|
52
|
+
def user(id:, **)
|
53
|
+
ctx[:user] = "User with id: #{id}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
action = SomeAction.call(id: 1)
|
58
|
+
|
59
|
+
action[:user] # => "User with id: 1"
|
60
|
+
|
61
|
+
action[:current_user] # => "User with id: 1"
|
62
|
+
|
63
|
+
action # =>
|
64
|
+
# Result: success
|
65
|
+
|
66
|
+
# Railway Flow:
|
67
|
+
# user -> AssignDoby
|
68
|
+
|
69
|
+
# Context:
|
70
|
+
# {:id=>1, :user=>"User with id: 1", :current_user=>"User with id: 1"}
|
71
|
+
|
72
|
+
# Errors:
|
73
|
+
# {}
|
74
|
+
```
|
75
|
+
|
76
|
+
## Behavior
|
77
|
+
|
78
|
+
- `doby` behaves similar to `step`, depending on `.call` method returning value(truthy or falsy) the execution will be moved to `success or failure` track accordingly.
|
79
|
+
- `doby` doesn't have `on_success, on_failure, if, unless, finish_him` options.
|
80
|
+
- All options passed after class constant will be passed as kwargs for `.call` method.
|
data/docs/doby.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../lib/decouplio'
|
2
|
+
|
3
|
+
class AssignDoby
|
4
|
+
def self.call(ctx:, to:, from: nil, value: nil)
|
5
|
+
raise 'from/value is empty' unless from || value
|
6
|
+
|
7
|
+
ctx[to] = value || ctx[from]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class SomeAction < Decouplio::Action
|
12
|
+
logic do
|
13
|
+
step :user
|
14
|
+
doby AssignDoby, to: :current_user, from: :user
|
15
|
+
end
|
16
|
+
|
17
|
+
def user(id:, **)
|
18
|
+
ctx[:user] = "User with id: #{id}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
action = SomeAction.call(id: 1)
|
23
|
+
|
24
|
+
puts action[:user] # => "User with id: 1"
|
25
|
+
|
26
|
+
puts action[:current_user] # => "User with id: 1"
|
27
|
+
|
28
|
+
puts action # =>
|
29
|
+
# Result: success
|
30
|
+
|
31
|
+
# Railway Flow:
|
32
|
+
# user -> AssignDoby
|
33
|
+
|
34
|
+
# Context:
|
35
|
+
# {:id=>1, :user=>"User with id: 1", :current_user=>"User with id: 1"}
|
36
|
+
|
37
|
+
# Errors:
|
38
|
+
# {}
|
data/docs/error_store.md
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
# Error store
|
2
|
+
|
3
|
+
It's an object to store errors. By default `Decouplio::DefaultErrorHandler` is used for `Decouplio::Action`
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
module Decouplio
|
7
|
+
class DefaultErrorHandler
|
8
|
+
attr_reader :errors
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@errors = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_error(key, message)
|
15
|
+
@errors.store(
|
16
|
+
key,
|
17
|
+
(@errors[key] || []) + [message].flatten
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge(error_store)
|
22
|
+
@errors = @errors.merge(error_store.errors) do |_key, this_val, other_val|
|
23
|
+
this_val + other_val
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
## How to use
|
31
|
+
Inside step method you can call `#add_error` method
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
add_error(key, message)
|
35
|
+
```
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'decouplio'
|
39
|
+
|
40
|
+
class SomeAction < Decouplio::Action
|
41
|
+
logic do
|
42
|
+
step :step_one
|
43
|
+
fail :fail_one
|
44
|
+
end
|
45
|
+
|
46
|
+
def step_one(**)
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def fail_one(**)
|
51
|
+
add_error(:something_went_wrong, 'Something went wrong')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
action = SomeAction.call
|
56
|
+
|
57
|
+
action # =>
|
58
|
+
# Result: failure
|
59
|
+
|
60
|
+
# Railway Flow:
|
61
|
+
# step_one -> fail_one
|
62
|
+
|
63
|
+
# Context:
|
64
|
+
# {}
|
65
|
+
|
66
|
+
# Errors:
|
67
|
+
# {:something_went_wrong=>["Something went wrong"]}
|
68
|
+
|
69
|
+
action.errors # =>
|
70
|
+
# {:something_went_wrong=>["Something went wrong"]}
|
71
|
+
```
|
72
|
+
|
73
|
+
## Behavior
|
74
|
+
|
75
|
+
- If error was added, it doesn't mean that action result is `failure`, action can be `success` and have errors, so basically `error store` is just a container for errors. Such behavior was implemented to provide more freedom.
|
76
|
+
- Error store for parent an inner action should be the same. It's because different error stores may have different `add_error` method signature and error hash structure.
|
77
|
+
|
78
|
+
## Custom error store
|
79
|
+
|
80
|
+
If you want to use your own custom error store then you can do it in this way:
|
81
|
+
|
82
|
+
- Define our own class with two public methods
|
83
|
+
- `#add_error(<signature you want>)` - method which adds error to `error_store`
|
84
|
+
- `#merge(error_store_to_merge)` - will be used by `Decouplio` to merge errors from inner actions to parent action.
|
85
|
+
- should have `attr_reader :errors`
|
86
|
+
|
87
|
+
<details><summary><b>EXAMPLE (CLICK ME)</b></summary>
|
88
|
+
<p>
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
require 'decouplio'
|
92
|
+
|
93
|
+
class CustomErrorStore
|
94
|
+
attr_reader :errors
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
@errors = {}
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_error(key:, message:, namespace: :root)
|
101
|
+
@errors[namespace] ||= {}
|
102
|
+
@errors[namespace].store(
|
103
|
+
key,
|
104
|
+
(@errors[namespace][key] || []) + [message].flatten
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def merge(error_store)
|
109
|
+
@errors = deep_merge(@errors, error_store.errors)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def deep_merge(this_hash, other_hash)
|
115
|
+
this_hash.merge(other_hash) do |_key, this_val, other_val|
|
116
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
117
|
+
deep_merge(this_val, other_val)
|
118
|
+
else
|
119
|
+
this_val + other_val
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class SomeActionWithCustomErrorStore < Decouplio::Action
|
126
|
+
error_store_class CustomErrorStore
|
127
|
+
|
128
|
+
logic do
|
129
|
+
step :step_one
|
130
|
+
step :step_two
|
131
|
+
end
|
132
|
+
|
133
|
+
def step_one(**)
|
134
|
+
add_error(
|
135
|
+
key: :under_root,
|
136
|
+
message: 'Error Message One'
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
def step_two(**)
|
141
|
+
add_error(
|
142
|
+
namespace: :step_two,
|
143
|
+
key: :error_happened,
|
144
|
+
message: 'Error Message Two'
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
action = SomeActionWithCustomErrorStore.call
|
150
|
+
|
151
|
+
action # =>
|
152
|
+
# Result: success
|
153
|
+
|
154
|
+
# Railway Flow:
|
155
|
+
# step_one -> step_two
|
156
|
+
|
157
|
+
# Context:
|
158
|
+
# {}
|
159
|
+
|
160
|
+
# Errors:
|
161
|
+
# {:root=>{:under_root=>["Error Message One"]}, :step_two=>{:error_happened=>["Error Message Two"]}}
|
162
|
+
|
163
|
+
action.errors # =>
|
164
|
+
# {:root=>{:under_root=>["Error Message One"]}, :step_two=>{:error_happened=>["Error Message Two"]}}
|
165
|
+
```
|
166
|
+
|
167
|
+
</p>
|
168
|
+
</details>
|
169
|
+
|
170
|
+
***
|
171
|
+
|
172
|
+
## Custom error store and inner action
|
173
|
+
|
174
|
+
### When error store is the same for parent and inner action
|
175
|
+
<details><summary><b>EXAMPLE (CLICK ME)</b></summary>
|
176
|
+
<p>
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
require 'decouplio'
|
180
|
+
|
181
|
+
class CustomErrorStore
|
182
|
+
attr_reader :errors
|
183
|
+
|
184
|
+
def initialize
|
185
|
+
@errors = {}
|
186
|
+
end
|
187
|
+
|
188
|
+
def add_error(key:, message:, namespace: :root)
|
189
|
+
@errors[namespace] ||= {}
|
190
|
+
@errors[namespace].store(
|
191
|
+
key,
|
192
|
+
(@errors[namespace][key] || []) + [message].flatten
|
193
|
+
)
|
194
|
+
end
|
195
|
+
|
196
|
+
def merge(error_store)
|
197
|
+
@errors = deep_merge(@errors, error_store.errors)
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def deep_merge(this_hash, other_hash)
|
203
|
+
this_hash.merge(other_hash) do |_key, this_val, other_val|
|
204
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
205
|
+
deep_merge(this_val, other_val)
|
206
|
+
else
|
207
|
+
this_val + other_val
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class InnerActionWithCustomErrorStore < Decouplio::Action
|
214
|
+
error_store_class CustomErrorStore
|
215
|
+
|
216
|
+
logic do
|
217
|
+
step :inner_step
|
218
|
+
end
|
219
|
+
|
220
|
+
def inner_step(**)
|
221
|
+
add_error(
|
222
|
+
namespace: :inner,
|
223
|
+
key: :inner_key,
|
224
|
+
message: 'Somebody was told me...'
|
225
|
+
)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class ParentActionWithCustomErrorStore < Decouplio::Action
|
230
|
+
error_store_class CustomErrorStore
|
231
|
+
|
232
|
+
logic do
|
233
|
+
step :step_one, action: InnerActionWithCustomErrorStore
|
234
|
+
step :step_two
|
235
|
+
end
|
236
|
+
|
237
|
+
def step_two(**)
|
238
|
+
add_error(
|
239
|
+
namespace: :parent,
|
240
|
+
key: :error_happened,
|
241
|
+
message: 'Message'
|
242
|
+
)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
action = ParentActionWithCustomErrorStore.call
|
247
|
+
|
248
|
+
puts action # =>
|
249
|
+
# Result: success
|
250
|
+
|
251
|
+
# Railway Flow:
|
252
|
+
# step_one -> inner_step -> step_two
|
253
|
+
|
254
|
+
# Context:
|
255
|
+
# {}
|
256
|
+
|
257
|
+
# Errors:
|
258
|
+
# {:inner=>{:inner_key=>["Somebody was told me..."]}, :parent=>{:error_happened=>["Message"]}}
|
259
|
+
|
260
|
+
puts action.errors # =>
|
261
|
+
# {:inner=>{:inner_key=>["Somebody was told me..."]}, :parent=>{:error_happened=>["Message"]}}
|
262
|
+
|
263
|
+
```
|
264
|
+
|
265
|
+
</p>
|
266
|
+
</details>
|
267
|
+
|
268
|
+
***
|
269
|
+
|
270
|
+
### When error store is different for parent and inner action
|
271
|
+
If inner action error store is different from parent action error store then error will be raised.
|
272
|
+
|
273
|
+
<details><summary><b>EXAMPLE (CLICK ME)</b></summary>
|
274
|
+
<p>
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
require 'decouplio'
|
278
|
+
|
279
|
+
class CustomErrorStore
|
280
|
+
attr_reader :errors
|
281
|
+
|
282
|
+
def initialize
|
283
|
+
@errors = {}
|
284
|
+
end
|
285
|
+
|
286
|
+
def add_error(key:, message:, namespace: :root)
|
287
|
+
@errors[namespace] ||= {}
|
288
|
+
@errors[namespace].store(
|
289
|
+
key,
|
290
|
+
(@errors[namespace][key] || []) + [message].flatten
|
291
|
+
)
|
292
|
+
end
|
293
|
+
|
294
|
+
def merge(error_store)
|
295
|
+
@errors = deep_merge(@errors, error_store.errors)
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
def deep_merge(this_hash, other_hash)
|
301
|
+
this_hash.merge(other_hash) do |_key, this_val, other_val|
|
302
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
303
|
+
deep_merge(this_val, other_val)
|
304
|
+
else
|
305
|
+
this_val + other_val
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class InnerActionWithDefaultErrorStore < Decouplio::Action
|
312
|
+
logic do
|
313
|
+
step :inner_step
|
314
|
+
end
|
315
|
+
|
316
|
+
def inner_step(**)
|
317
|
+
add_error(
|
318
|
+
key: :inner_key,
|
319
|
+
message: 'Somebody was told me...'
|
320
|
+
)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class ParentActionForInnerActionDefaultErrorStore < Decouplio::Action
|
325
|
+
error_store_class CustomErrorStore
|
326
|
+
|
327
|
+
logic do
|
328
|
+
step :step_one, action: InnerActionWithDefaultErrorStore
|
329
|
+
step :step_two
|
330
|
+
end
|
331
|
+
|
332
|
+
def step_two(**)
|
333
|
+
add_error(
|
334
|
+
namespace: :parent,
|
335
|
+
key: :error_happened,
|
336
|
+
message: 'Message'
|
337
|
+
)
|
338
|
+
end
|
339
|
+
end # =>
|
340
|
+
# Error store for action and inner action should be the same. (Decouplio::Errors::ErrorStoreError)
|
341
|
+
|
342
|
+
```
|
343
|
+
|
344
|
+
</p>
|
345
|
+
</details>
|
346
|
+
|
347
|
+
***
|
data/docs/error_store.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
require_relative '../lib/decouplio'
|
2
|
+
|
3
|
+
# How to use
|
4
|
+
class SomeAction < Decouplio::Action
|
5
|
+
logic do
|
6
|
+
step :step_one
|
7
|
+
fail :fail_one
|
8
|
+
end
|
9
|
+
|
10
|
+
def step_one(**)
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def fail_one(**)
|
15
|
+
add_error(:something_went_wrong, 'Something went wrong')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
action = SomeAction.call
|
20
|
+
|
21
|
+
puts action # =>
|
22
|
+
# Result: failure
|
23
|
+
|
24
|
+
# Railway Flow:
|
25
|
+
# step_one -> fail_one
|
26
|
+
|
27
|
+
# Context:
|
28
|
+
# {}
|
29
|
+
|
30
|
+
# Errors:
|
31
|
+
# {:something_went_wrong=>["Something went wrong"]}
|
32
|
+
|
33
|
+
puts action.errors # =>
|
34
|
+
# {:something_went_wrong=>["Something went wrong"]}
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
# Custom error store
|
40
|
+
class CustomErrorStore
|
41
|
+
attr_reader :errors
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@errors = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_error(key:, message:, namespace: :root)
|
48
|
+
@errors[namespace] ||= {}
|
49
|
+
@errors[namespace].store(
|
50
|
+
key,
|
51
|
+
(@errors[namespace][key] || []) + [message].flatten
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def merge(error_store)
|
56
|
+
@errors = deep_merge(@errors, error_store.errors)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def deep_merge(this_hash, other_hash)
|
62
|
+
this_hash.merge(other_hash) do |_key, this_val, other_val|
|
63
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
64
|
+
deep_merge(this_val, other_val)
|
65
|
+
else
|
66
|
+
this_val + other_val
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class SomeActionWithCustomErrorStore < Decouplio::Action
|
73
|
+
error_store_class CustomErrorStore
|
74
|
+
|
75
|
+
logic do
|
76
|
+
step :step_one
|
77
|
+
step :step_two
|
78
|
+
end
|
79
|
+
|
80
|
+
def step_one(**)
|
81
|
+
add_error(
|
82
|
+
key: :under_root,
|
83
|
+
message: 'Error Message One'
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
def step_two(**)
|
88
|
+
add_error(
|
89
|
+
namespace: :step_two,
|
90
|
+
key: :error_happened,
|
91
|
+
message: 'Error Message Two'
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
action = SomeActionWithCustomErrorStore.call
|
97
|
+
|
98
|
+
puts action # =>
|
99
|
+
# Result: success
|
100
|
+
|
101
|
+
# Railway Flow:
|
102
|
+
# step_one -> step_two
|
103
|
+
|
104
|
+
# Context:
|
105
|
+
# {}
|
106
|
+
|
107
|
+
# Errors:
|
108
|
+
# {:root=>{:under_root=>["Error Message One"]}, :step_two=>{:error_happened=>["Error Message Two"]}}
|
109
|
+
|
110
|
+
puts action.errors # =>
|
111
|
+
# {:root=>{:under_root=>["Error Message One"]}, :step_two=>{:error_happened=>["Error Message Two"]}}
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
# When error store is the same for parent and inner action
|
117
|
+
|
118
|
+
class InnerActionWithCustomErrorStore < Decouplio::Action
|
119
|
+
error_store_class CustomErrorStore
|
120
|
+
|
121
|
+
logic do
|
122
|
+
step :inner_step
|
123
|
+
end
|
124
|
+
|
125
|
+
def inner_step(**)
|
126
|
+
add_error(
|
127
|
+
namespace: :inner,
|
128
|
+
key: :inner_key,
|
129
|
+
message: 'Somebody was told me...'
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class ParentActionWithCustomErrorStore < Decouplio::Action
|
135
|
+
error_store_class CustomErrorStore
|
136
|
+
|
137
|
+
logic do
|
138
|
+
step :step_one, action: InnerActionWithCustomErrorStore
|
139
|
+
step :step_two
|
140
|
+
end
|
141
|
+
|
142
|
+
def step_two(**)
|
143
|
+
add_error(
|
144
|
+
namespace: :parent,
|
145
|
+
key: :error_happened,
|
146
|
+
message: 'Message'
|
147
|
+
)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
action = ParentActionWithCustomErrorStore.call
|
152
|
+
|
153
|
+
puts action # =>
|
154
|
+
# Result: success
|
155
|
+
|
156
|
+
# Railway Flow:
|
157
|
+
# step_one -> inner_step -> step_two
|
158
|
+
|
159
|
+
# Context:
|
160
|
+
# {}
|
161
|
+
|
162
|
+
# Errors:
|
163
|
+
# {:inner=>{:inner_key=>["Somebody was told me..."]}, :parent=>{:error_happened=>["Message"]}}
|
164
|
+
|
165
|
+
puts action.errors # =>
|
166
|
+
# {:inner=>{:inner_key=>["Somebody was told me..."]}, :parent=>{:error_happened=>["Message"]}}
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
# When error store is different for parent and inner action
|
172
|
+
|
173
|
+
class InnerActionWithDefaultErrorStore < Decouplio::Action
|
174
|
+
logic do
|
175
|
+
step :inner_step
|
176
|
+
end
|
177
|
+
|
178
|
+
def inner_step(**)
|
179
|
+
add_error(
|
180
|
+
key: :inner_key,
|
181
|
+
message: 'Somebody was told me...'
|
182
|
+
)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class ParentActionForInnerActionDefaultErrorStore < Decouplio::Action
|
187
|
+
error_store_class CustomErrorStore
|
188
|
+
|
189
|
+
logic do
|
190
|
+
step :step_one, action: InnerActionWithDefaultErrorStore
|
191
|
+
step :step_two
|
192
|
+
end
|
193
|
+
|
194
|
+
def step_two(**)
|
195
|
+
add_error(
|
196
|
+
namespace: :parent,
|
197
|
+
key: :error_happened,
|
198
|
+
message: 'Message'
|
199
|
+
)
|
200
|
+
end
|
201
|
+
end # =>
|
202
|
+
# Error store for action and inner action should be the same. (Decouplio::Errors::ErrorStoreError)
|