flows 0.1.0 → 0.5.1

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