flows 0.1.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +38 -0
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -0
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +44 -2
  7. data/.ruby-version +1 -1
  8. data/.yardopts +1 -0
  9. data/CHANGELOG.md +65 -0
  10. data/README.md +186 -256
  11. data/Rakefile +35 -1
  12. data/bin/.rubocop.yml +5 -0
  13. data/bin/all_the_errors +55 -0
  14. data/bin/benchmark +69 -78
  15. data/bin/benchmark_cli/compare.rb +118 -0
  16. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  17. data/bin/benchmark_cli/compare/base.rb +45 -0
  18. data/bin/benchmark_cli/compare/command.rb +47 -0
  19. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  20. data/bin/benchmark_cli/examples.rb +23 -0
  21. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  22. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  30. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  32. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  37. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  38. data/bin/benchmark_cli/helpers.rb +12 -0
  39. data/bin/benchmark_cli/ruby.rb +15 -0
  40. data/bin/benchmark_cli/ruby/command.rb +38 -0
  41. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  42. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  43. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  44. data/bin/console +1 -0
  45. data/bin/docserver +7 -0
  46. data/bin/errors +130 -0
  47. data/bin/errors_cli/contract_error_demo.rb +49 -0
  48. data/bin/errors_cli/di_error_demo.rb +38 -0
  49. data/bin/errors_cli/flow_error_demo.rb +22 -0
  50. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  51. data/bin/errors_cli/oc_error_demo.rb +40 -0
  52. data/bin/errors_cli/railway_error_demo.rb +10 -0
  53. data/bin/errors_cli/result_error_demo.rb +13 -0
  54. data/bin/errors_cli/scp_error_demo.rb +17 -0
  55. data/docs/.nojekyll +0 -0
  56. data/docs/README.md +13 -0
  57. data/docs/_sidebar.md +2 -0
  58. data/docs/index.html +30 -0
  59. data/flows.gemspec +27 -2
  60. data/forspell.dict +17 -0
  61. data/lefthook.yml +21 -0
  62. data/lib/flows.rb +13 -5
  63. data/lib/flows/contract.rb +402 -0
  64. data/lib/flows/contract/array.rb +55 -0
  65. data/lib/flows/contract/case_eq.rb +43 -0
  66. data/lib/flows/contract/compose.rb +77 -0
  67. data/lib/flows/contract/either.rb +53 -0
  68. data/lib/flows/contract/error.rb +25 -0
  69. data/lib/flows/contract/hash.rb +75 -0
  70. data/lib/flows/contract/hash_of.rb +70 -0
  71. data/lib/flows/contract/helpers.rb +22 -0
  72. data/lib/flows/contract/predicate.rb +34 -0
  73. data/lib/flows/contract/transformer.rb +50 -0
  74. data/lib/flows/contract/tuple.rb +70 -0
  75. data/lib/flows/flow.rb +96 -7
  76. data/lib/flows/flow/errors.rb +29 -0
  77. data/lib/flows/flow/node.rb +132 -0
  78. data/lib/flows/flow/router.rb +29 -0
  79. data/lib/flows/flow/router/custom.rb +59 -0
  80. data/lib/flows/flow/router/errors.rb +11 -0
  81. data/lib/flows/flow/router/simple.rb +25 -0
  82. data/lib/flows/plugin.rb +14 -0
  83. data/lib/flows/plugin/dependency_injector.rb +159 -0
  84. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  85. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  86. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  87. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  88. data/lib/flows/plugin/implicit_init.rb +45 -0
  89. data/lib/flows/plugin/output_contract.rb +85 -0
  90. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  91. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  92. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  93. data/lib/flows/plugin/profiler.rb +114 -0
  94. data/lib/flows/plugin/profiler/injector.rb +35 -0
  95. data/lib/flows/plugin/profiler/report.rb +48 -0
  96. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  97. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  98. data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
  99. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  100. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  101. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  102. data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
  103. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  104. data/lib/flows/railway.rb +154 -0
  105. data/lib/flows/railway/dsl.rb +18 -0
  106. data/lib/flows/railway/errors.rb +17 -0
  107. data/lib/flows/railway/step.rb +24 -0
  108. data/lib/flows/railway/step_list.rb +38 -0
  109. data/lib/flows/result.rb +189 -2
  110. data/lib/flows/result/do.rb +172 -0
  111. data/lib/flows/result/err.rb +12 -6
  112. data/lib/flows/result/errors.rb +29 -17
  113. data/lib/flows/result/helpers.rb +25 -3
  114. data/lib/flows/result/ok.rb +12 -6
  115. data/lib/flows/shared_context_pipeline.rb +299 -0
  116. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  117. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
  118. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  119. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  120. data/lib/flows/shared_context_pipeline/mutation_step.rb +29 -0
  121. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  122. data/lib/flows/shared_context_pipeline/step.rb +44 -0
  123. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  124. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  125. data/lib/flows/shared_context_pipeline/wrap.rb +74 -0
  126. data/lib/flows/util.rb +17 -0
  127. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  128. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +98 -0
  129. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  130. data/lib/flows/util/prepend_to_class.rb +179 -0
  131. data/lib/flows/version.rb +1 -1
  132. metadata +288 -20
  133. data/.travis.yml +0 -8
  134. data/Gemfile.lock +0 -119
  135. data/bin/demo +0 -66
  136. data/bin/examples.rb +0 -159
  137. data/bin/profile_10steps +0 -64
  138. data/bin/ruby_benchmarks +0 -26
  139. data/lib/flows/node.rb +0 -27
  140. data/lib/flows/operation.rb +0 -54
  141. data/lib/flows/operation/builder.rb +0 -130
  142. data/lib/flows/operation/builder/build_router.rb +0 -37
  143. data/lib/flows/operation/dsl.rb +0 -72
  144. data/lib/flows/operation/errors.rb +0 -75
  145. data/lib/flows/operation/executor.rb +0 -78
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
@@ -1,25 +1,31 @@
1
1
  module Flows
2
2
  class Result
3
- # Wrapper for failure results
3
+ # Result Object for failure results.
4
+ #
5
+ # @see Flows::Result behaviour described here
4
6
  class Err < Result
5
- attr_reader :error
6
-
7
- def initialize(data, status: :failure, meta: {})
8
- @error = data
7
+ def initialize(data, status: :err, meta: {})
8
+ @data = data
9
9
  @status = status
10
10
  @meta = meta
11
11
  end
12
12
 
13
+ def error
14
+ @data
15
+ end
16
+
17
+ # @return [false]
13
18
  def ok?
14
19
  false
15
20
  end
16
21
 
22
+ # @return [true]
17
23
  def err?
18
24
  true
19
25
  end
20
26
 
21
27
  def unwrap
22
- raise UnwrapError.new(@status, @data, @meta)
28
+ raise AccessError, self
23
29
  end
24
30
  end
25
31
  end
@@ -1,29 +1,41 @@
1
1
  module Flows
2
2
  class Result
3
- # Error for unwrapping non-successful result object
4
- class UnwrapError < Flows::Error
5
- def initialize(status, data, meta)
6
- @status = status
7
- @data = data
8
- @meta = meta
3
+ # Base class for Result errors.
4
+ class Error < ::Flows::Error; end
5
+
6
+ # Error for invalid data access cases
7
+ class AccessError < Flows::Error
8
+ def initialize(result)
9
+ @result = result
9
10
  end
10
11
 
11
12
  def message
12
- "You're trying to unwrap non-successful result with status `#{@status.inspect}` and data `#{@data.inspect}`\n\
13
- Result metadata: `#{@meta.inspect}`"
13
+ [
14
+ base_msg,
15
+ " Result status: `#{@result.status.inspect}`",
16
+ " Result data: `#{data.inspect}`",
17
+ " Result meta: `#{@result.meta.inspect}`"
18
+ ].join("\n")
14
19
  end
15
- end
16
20
 
17
- # Error for dealing with failure result as success one
18
- class NoErrorError < Flows::Error
19
- def initialize(status, data)
20
- @status = status
21
- @data = data
21
+ private
22
+
23
+ def base_msg
24
+ case @result
25
+ when Flows::Result::Ok
26
+ 'Data in a successful result must be retrieved using `#unwrap` method, not `#error`.'
27
+ when Flows::Result::Err
28
+ 'Data in a failure result must be retrieved using `#error` method, not `#unwrap`.'
29
+ end
22
30
  end
23
31
 
24
- def message
25
- "You're trying to get error data for successful result with status \
26
- `#{@status.inspect}` and data `#{@data.inspect}`"
32
+ def data
33
+ case @result
34
+ when Flows::Result::Ok
35
+ @result.unwrap
36
+ when Flows::Result::Err
37
+ @result.error
38
+ end
27
39
  end
28
40
  end
29
41
  end
@@ -1,14 +1,36 @@
1
1
  module Flows
2
2
  class Result
3
- # Shortcuts for building result objects
3
+ # Shortcuts for building and matching result objects.
4
+ #
5
+ # `:reek:UtilityFunction` and `:reek:FeatureEnvy` checks should be disabled here
6
+ # because this module is intended to contain private utility methods only.
7
+ #
8
+ # This module defines the following private methods:
9
+ #
10
+ # * `ok(status = :ok, **data)` - for building successful results from a hash of keyword arguments.
11
+ # * `ok_data(data, status: :ok)` - for building successful results from any data.
12
+ # * `err(status = :err, **data)` - for building failure results from a hash of keyword arguments.
13
+ # * `err_data(data, status: :err)` - for building failure results from any data.
14
+ # * `match_ok(status = nil)` - for case matching against successful results.
15
+ # * `match_err(status = nil)` - for case matching against failure results.
16
+ #
17
+ # @see Flows::Result usage examples provided here
4
18
  module Helpers
5
19
  private
6
20
 
7
- def ok(status = :success, **data)
21
+ def ok(status = :ok, **data)
8
22
  Flows::Result::Ok.new(data, status: status)
9
23
  end
10
24
 
11
- def err(status = :failure, **data)
25
+ def ok_data(data, status: :ok)
26
+ Flows::Result::Ok.new(data, status: status)
27
+ end
28
+
29
+ def err(status = :err, **data)
30
+ Flows::Result::Err.new(data, status: status)
31
+ end
32
+
33
+ def err_data(data, status: :err)
12
34
  Flows::Result::Err.new(data, status: status)
13
35
  end
14
36
 
@@ -1,25 +1,31 @@
1
1
  module Flows
2
2
  class Result
3
- # Wrapper for successful results
3
+ # Result Object for successful results.
4
+ #
5
+ # @see Flows::Result behaviour described here
4
6
  class Ok < Result
5
- attr_reader :unwrap
6
-
7
- def initialize(data, status: :success, meta: {})
8
- @unwrap = data
7
+ def initialize(data, status: :ok, meta: {})
8
+ @data = data
9
9
  @status = status
10
10
  @meta = meta
11
11
  end
12
12
 
13
+ def unwrap
14
+ @data
15
+ end
16
+
17
+ # @return [true]
13
18
  def ok?
14
19
  true
15
20
  end
16
21
 
22
+ # @return [false]
17
23
  def err?
18
24
  false
19
25
  end
20
26
 
21
27
  def error
22
- raise NoErrorError.new(@status, @data)
28
+ raise AccessError, self
23
29
  end
24
30
  end
25
31
  end
@@ -0,0 +1,299 @@
1
+ require_relative 'shared_context_pipeline/errors'
2
+ require_relative 'shared_context_pipeline/router_definition'
3
+ require_relative 'shared_context_pipeline/step'
4
+ require_relative 'shared_context_pipeline/mutation_step'
5
+ require_relative 'shared_context_pipeline/track'
6
+ require_relative 'shared_context_pipeline/track_list'
7
+ require_relative 'shared_context_pipeline/wrap'
8
+ require_relative 'shared_context_pipeline/dsl'
9
+
10
+ module Flows
11
+ # Abstraction for organizing calculations in a shared data context.
12
+ #
13
+ # Let's start with example. Let's say we have to calculate `(a + b) * (a - b)`:
14
+ #
15
+ # class Claculation < Flows::SharedContextPipeline
16
+ # step :calc_left_part
17
+ # step :calc_right_part
18
+ # step :calc_result
19
+ #
20
+ # def calc_left_part(a:, b:, **)
21
+ # ok(left: a + b)
22
+ # end
23
+ #
24
+ # def calc_right_part(a:, b:, **)
25
+ # ok(right: a - b)
26
+ # end
27
+ #
28
+ # def calc_result(left:, right:, **)
29
+ # ok(result: left * right)
30
+ # end
31
+ # end
32
+ #
33
+ # x = Calculation.call(a: 1, b: 2)
34
+ # # x is a `Flows::Result::Ok`
35
+ #
36
+ # x.unwrap
37
+ # # => { a: 1, b: 2, left: 3, right: -1, result: -3 }
38
+ #
39
+ # It works by the following rules:
40
+ #
41
+ # * execution context is a Hash with Symbol keys.
42
+ # * input becomes initial execution context.
43
+ # * steps are executed in a provided order.
44
+ # * actual execution context becomes a step input.
45
+ # * step implementation is a public method with the same name.
46
+ # * step implementation must return {Flows::Result} ({Flows::Result::Helpers} already included).
47
+ # * Result Object data will be merged to shared context after each step execution.
48
+ # * If returned Result Object is successful - next step will be executed,
49
+ # in the case of the last step a calculation will be finished
50
+ # * If returned Result Object is failure - a calculation will be finished
51
+ # * When calculation is finished a Result Object will be returned:
52
+ # * result will have the same type and status as in the last executed step result
53
+ # * result wull have a full execution context as data
54
+ #
55
+ # ## Mutation Steps
56
+ #
57
+ # You may use a different step definition way:
58
+ #
59
+ # class MyClass < Flows::SharedContextPipeline
60
+ # mut_step :hello
61
+ #
62
+ # def hello(ctx)
63
+ # ctx[:result] = 'hello'
64
+ # end
65
+ # end
66
+ #
67
+ # When you use `mut_step` DSL method you define a step with different rules for implementation:
68
+ #
69
+ # * step implementation receives _one_ argument and it's your execution context in a form of a mutable Hash
70
+ # * step implementation can modify execution context
71
+ # * if step implementation returns
72
+ # * "truly" value - it makes step successful with default status `:ok`
73
+ # * "falsey" value - it makes step failure with default status `:err`
74
+ # * {Result} - it works like for standard step, but data is ignored. Only result type and status have effect.
75
+ #
76
+ # ## Tracks & Routes
77
+ #
78
+ # In some situations you may want some branching in a mix. Let's provide an example for a common problem
79
+ # when you have to do some additional steps in case of multiple types of errors.
80
+ # Let's say report to some external system:
81
+ #
82
+ # class SafeFetchComment < Flows::SharedContextPipeline
83
+ # step :fetch_post, routes(
84
+ # match_ok => :next,
85
+ # match_err => :handle_error
86
+ # )
87
+ # step :fetch_comment, routes(
88
+ # match_ok => :next,
89
+ # match_err => :handle_error
90
+ # )
91
+ #
92
+ # track :handle_error do
93
+ # step :report_to_external_system
94
+ # step :write_logs
95
+ # end
96
+ #
97
+ # # steps implementations here
98
+ # end
99
+ #
100
+ # Let's describe how `routes(...)` and `track` DSL methods work.
101
+ #
102
+ # Each step has a router. Router is defined by a hash and `router(...)` method itself is
103
+ # a (almost) shortcut to {Flows::Flow::Router::Custom} constructor. By default each step has the following
104
+ # router definition:
105
+ #
106
+ # {
107
+ # match_ok => :next, # next step is calculated by DSL, this symbol will be substituted in a final router
108
+ # match_err => :end # `:end` means stop execution
109
+ # }
110
+ #
111
+ # Hash provided in `router(...)` method will override default hash to make a final router.
112
+ #
113
+ # Because of symbols with special behavior (`:end`, `:next`) you cannot name your steps or tracks
114
+ # `:next` or `:end`. And this is totally ok because it is reserved words in Ruby.
115
+ #
116
+ # By the way, you can route not only to tracks, but also to steps. And by using `match_ok(status)` and
117
+ # `match_err(status)` you can have different routes for different statuses of successful or failure results.
118
+ #
119
+ # Steps defined inside a track are fully isolated. The simple way is to think about track as a totally
120
+ # separate pipeline. You have to explicitly enter to it. And explicitly return from it to root-level steps
121
+ # if you want to continue execution.
122
+ #
123
+ # If you feel it's too much verbose to route many steps to the same track you can do something like this:
124
+ #
125
+ # class SafeFetchComment < Flows::SharedContextPipeline
126
+ # def self.safe_step(name)
127
+ # step name, routes(
128
+ # match_ok => :next,
129
+ # match_err => :handle_error
130
+ # )
131
+ # end
132
+ #
133
+ # safe_step :fetch_post
134
+ # safe_step :fetch_comment
135
+ #
136
+ # track :handle_error do
137
+ # step :report_to_external_system
138
+ # step :write_logs
139
+ # end
140
+ #
141
+ # # steps implementations here
142
+ # end
143
+ #
144
+ # ## Wrappers
145
+ #
146
+ # Sometimes you have to execute some steps inside SQL-transaction or something like this.
147
+ # Most frameworks allow to do it in the following approach:
148
+ #
149
+ # SQLDataBase.transaction do
150
+ # # your steps are executed here
151
+ # # special error must be executed to cancel the transaction
152
+ # end
153
+ #
154
+ # It's impossible to do with just step or track DSL. That's why `wrap` DSL method has been added.
155
+ # Let's review it on example:
156
+ #
157
+ # class MySCP < Flows::SharedContextPipeline
158
+ # step :some_preparations
159
+ # wrap :in_transaction do
160
+ # step :action_a
161
+ # step :action_b
162
+ # end
163
+ #
164
+ # def in_transaction(ctx, meta, &block)
165
+ # result = nil
166
+ #
167
+ # ActiveRecord::Base.transaction do
168
+ # result = block.call
169
+ #
170
+ # raise ActiveRecord::Rollback if result.err?
171
+ # end
172
+ #
173
+ # result
174
+ # end
175
+ #
176
+ # # step implementations here
177
+ # end
178
+ #
179
+ # `wrap` DSL method receives name and block. Inside block you can define steps and tracks.
180
+ #
181
+ # `wrap` makes an isolated track and step structure.
182
+ # You cannot route between wrapped and unwrapped steps and tracks.
183
+ # One exception - you can route to the first wrapped step.
184
+ #
185
+ # The same wrapper with the same name can be used multiple times in the same operation:
186
+ #
187
+ # class MySCP < Flows::SharedContextPipeline
188
+ # step :some_preparations
189
+ # wrap :in_transaction do
190
+ # step :action_a
191
+ # step :action_b
192
+ # end
193
+ # step :some_calculations
194
+ # wrap :in_transaction do
195
+ # step :action_c
196
+ # step :action_d
197
+ # end
198
+ #
199
+ # # ...
200
+ # end
201
+ #
202
+ # Unlike step implementations wrapper implementation has access to a shared meta (can be useful for plugins).
203
+ #
204
+ # You may think about steps and tracks inside wrapper as a nested pipeline.
205
+ # Wrapper implementation receives mutable data context, metadata and block.
206
+ # Block execution (`block.call`) returns a result object of the executed "nested pipeline".
207
+ #
208
+ # When you route to `:end` inside wrapper - you're leaving wrapper, **not** the whole pipeline.
209
+ #
210
+ # From the execution perspective wrapper is a single step. The step name is the first wrapped step name.
211
+ #
212
+ # `wrap` itself also can have overriden routes table:
213
+ #
214
+ # wrap :in_transaction, routes(match_ok => :next, match_err => :end) do
215
+ # # steps...
216
+ # end
217
+ #
218
+ # Like a step, wrapper implementation must return {Flows::Result}.
219
+ # Result is processed with the same approach as for normal step.
220
+ # **Do not modify result returned from block - build a new one if needed.
221
+ # Otherwise mutation steps can be broken.**
222
+ #
223
+ # ## Callbacks and metadata
224
+ #
225
+ # You may want to have some logic to execute before all steps, or after all, or before each, or after each.
226
+ # For example to inject generalized execution process logging.
227
+ # To achieve this you can use callbacks:
228
+ #
229
+ # class MySCP < Flows::SharedContextPipeline
230
+ # before_all do |klass, data, meta|
231
+ # # you can modify execution data context and metadata here
232
+ # # return value will be ignored
233
+ # end
234
+ #
235
+ # after_all do |klass, pipeline_result, data, meta|
236
+ # # you can modify execution data context and metadata here
237
+ # # you must return a final result object here
238
+ # # if no modifications needed - just return provided pipeline_result
239
+ # end
240
+ #
241
+ # before_each do |klass, step_name, data, meta|
242
+ # # you can modify execution data context and metadata here
243
+ # # return value will be ignored
244
+ # end
245
+ #
246
+ # after_each do |klass, step_name, step_result, data, meta|
247
+ # # you can modify execution data context and metadata here
248
+ # # return value will be ignored
249
+ # #
250
+ # # callback executed after context is updated with result data
251
+ # # (in the case of normal steps, mutation steps update context directly)
252
+ # #
253
+ # # DO NOT MODIFY RESULT OBJECT HERE - IT CAN BROKE MUTATION STEPS
254
+ # end
255
+ # end
256
+ #
257
+ # Metadata - is a Hash which is shared between step executions.
258
+ # This hash becomes metadata of a final {Flows::Result}.
259
+ #
260
+ # Metadata is designed to store non-business data such as execution times,
261
+ # some library specific data, and so on.
262
+ class SharedContextPipeline
263
+ extend ::Flows::Plugin::ImplicitInit
264
+
265
+ include ::Flows::Result::Helpers
266
+ extend ::Flows::Result::Helpers
267
+
268
+ extend DSL
269
+
270
+ def initialize
271
+ @__flow = self.class.tracks.to_flow(self)
272
+ end
273
+
274
+ # Executes pipeline with provided keyword arguments, returns Result Object.
275
+ #
276
+ # @return [Flows::Result]
277
+ def call(**data) # rubocop:disable Metrics/MethodLength
278
+ klass = self.class
279
+ meta = {}
280
+ context = { data: data, meta: meta, class: klass }
281
+
282
+ klass.before_all_callbacks.each do |callback|
283
+ callback.call(klass, data, meta)
284
+ end
285
+
286
+ flow_result = @__flow.call(nil, context: context)
287
+
288
+ final_result = flow_result.class.new(
289
+ data,
290
+ status: flow_result.status,
291
+ meta: meta
292
+ )
293
+
294
+ klass.after_all_callbacks.reduce(final_result) do |result, callback|
295
+ callback.call(klass, result, data, meta)
296
+ end
297
+ end
298
+ end
299
+ end