flows 0.3.0 → 0.4.0

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/{build.yml → test.yml} +5 -10
  3. data/.gitignore +1 -0
  4. data/.reek.yml +42 -0
  5. data/.rubocop.yml +20 -7
  6. data/.ruby-version +1 -1
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +42 -0
  9. data/Gemfile +0 -6
  10. data/Gemfile.lock +139 -74
  11. data/README.md +158 -364
  12. data/Rakefile +35 -1
  13. data/bin/.rubocop.yml +5 -0
  14. data/bin/all_the_errors +47 -0
  15. data/bin/benchmark +73 -105
  16. data/bin/benchmark_cli/compare.rb +118 -0
  17. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  18. data/bin/benchmark_cli/compare/base.rb +45 -0
  19. data/bin/benchmark_cli/compare/command.rb +47 -0
  20. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  21. data/bin/benchmark_cli/examples.rb +23 -0
  22. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  30. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  32. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  37. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  38. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  39. data/bin/benchmark_cli/helpers.rb +12 -0
  40. data/bin/benchmark_cli/ruby.rb +15 -0
  41. data/bin/benchmark_cli/ruby/command.rb +38 -0
  42. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  43. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  44. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  45. data/bin/console +1 -0
  46. data/bin/docserver +7 -0
  47. data/bin/errors +118 -0
  48. data/bin/errors_cli/contract_error_demo.rb +49 -0
  49. data/bin/errors_cli/di_error_demo.rb +38 -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/README.md +2 -186
  56. data/docs/_sidebar.md +0 -24
  57. data/docs/index.html +1 -1
  58. data/flows.gemspec +25 -2
  59. data/forspell.dict +9 -0
  60. data/lefthook.yml +9 -0
  61. data/lib/flows.rb +11 -5
  62. data/lib/flows/contract.rb +402 -0
  63. data/lib/flows/contract/array.rb +55 -0
  64. data/lib/flows/contract/case_eq.rb +41 -0
  65. data/lib/flows/contract/compose.rb +77 -0
  66. data/lib/flows/contract/either.rb +53 -0
  67. data/lib/flows/contract/error.rb +25 -0
  68. data/lib/flows/contract/hash.rb +75 -0
  69. data/lib/flows/contract/hash_of.rb +70 -0
  70. data/lib/flows/contract/helpers.rb +22 -0
  71. data/lib/flows/contract/predicate.rb +34 -0
  72. data/lib/flows/contract/transformer.rb +50 -0
  73. data/lib/flows/contract/tuple.rb +70 -0
  74. data/lib/flows/flow.rb +75 -7
  75. data/lib/flows/flow/node.rb +131 -0
  76. data/lib/flows/flow/router.rb +25 -0
  77. data/lib/flows/flow/router/custom.rb +54 -0
  78. data/lib/flows/flow/router/errors.rb +11 -0
  79. data/lib/flows/flow/router/simple.rb +20 -0
  80. data/lib/flows/plugin.rb +13 -0
  81. data/lib/flows/plugin/dependency_injector.rb +159 -0
  82. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  83. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  84. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  85. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  86. data/lib/flows/plugin/implicit_init.rb +45 -0
  87. data/lib/flows/plugin/output_contract.rb +84 -0
  88. data/lib/flows/plugin/output_contract/dsl.rb +36 -0
  89. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  90. data/lib/flows/plugin/output_contract/wrapper.rb +53 -0
  91. data/lib/flows/railway.rb +140 -37
  92. data/lib/flows/railway/dsl.rb +8 -19
  93. data/lib/flows/railway/errors.rb +8 -12
  94. data/lib/flows/railway/step.rb +24 -0
  95. data/lib/flows/railway/step_list.rb +38 -0
  96. data/lib/flows/result.rb +188 -2
  97. data/lib/flows/result/do.rb +160 -16
  98. data/lib/flows/result/err.rb +12 -6
  99. data/lib/flows/result/errors.rb +29 -17
  100. data/lib/flows/result/helpers.rb +25 -3
  101. data/lib/flows/result/ok.rb +12 -6
  102. data/lib/flows/shared_context_pipeline.rb +216 -0
  103. data/lib/flows/shared_context_pipeline/dsl.rb +63 -0
  104. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  105. data/lib/flows/shared_context_pipeline/mutation_step.rb +31 -0
  106. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  107. data/lib/flows/shared_context_pipeline/step.rb +46 -0
  108. data/lib/flows/shared_context_pipeline/track.rb +67 -0
  109. data/lib/flows/shared_context_pipeline/track_list.rb +46 -0
  110. data/lib/flows/util.rb +17 -0
  111. data/lib/flows/util/inheritable_singleton_vars.rb +79 -0
  112. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +109 -0
  113. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +104 -0
  114. data/lib/flows/util/prepend_to_class.rb +145 -0
  115. data/lib/flows/version.rb +1 -1
  116. metadata +233 -37
  117. data/bin/demo +0 -66
  118. data/bin/examples.rb +0 -195
  119. data/bin/profile_10steps +0 -106
  120. data/bin/ruby_benchmarks +0 -26
  121. data/docs/CNAME +0 -1
  122. data/docs/contributing/benchmarks_profiling.md +0 -3
  123. data/docs/contributing/local_development.md +0 -3
  124. data/docs/flow/direct_usage.md +0 -3
  125. data/docs/flow/general_idea.md +0 -3
  126. data/docs/operation/basic_usage.md +0 -1
  127. data/docs/operation/inject_steps.md +0 -3
  128. data/docs/operation/lambda_steps.md +0 -3
  129. data/docs/operation/result_shapes.md +0 -3
  130. data/docs/operation/routing_tracks.md +0 -3
  131. data/docs/operation/wrapping_steps.md +0 -3
  132. data/docs/overview/performance.md +0 -336
  133. data/docs/railway/basic_usage.md +0 -232
  134. data/docs/result_objects/basic_usage.md +0 -196
  135. data/docs/result_objects/do_notation.md +0 -139
  136. data/lib/flows/implicit_build.rb +0 -16
  137. data/lib/flows/node.rb +0 -27
  138. data/lib/flows/operation.rb +0 -55
  139. data/lib/flows/operation/builder.rb +0 -130
  140. data/lib/flows/operation/builder/build_router.rb +0 -37
  141. data/lib/flows/operation/dsl.rb +0 -93
  142. data/lib/flows/operation/errors.rb +0 -75
  143. data/lib/flows/operation/executor.rb +0 -78
  144. data/lib/flows/railway/builder.rb +0 -68
  145. data/lib/flows/railway/executor.rb +0 -23
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
@@ -0,0 +1,36 @@
1
+ module Flows
2
+ module Plugin
3
+ module OutputContract
4
+ # DSL for OutputContract plugin.
5
+ module DSL
6
+ # Hash of contracts for successful results.
7
+ attr_reader :success_contracts
8
+
9
+ # Hash of contracts for failure results.
10
+ attr_reader :failure_contracts
11
+
12
+ Flows::Util::InheritableSingletonVars::DupStrategy.call(
13
+ self,
14
+ '@success_contracts' => {},
15
+ '@failure_contracts' => {}
16
+ )
17
+
18
+ # Defines a contract for a successful result with specific status.
19
+ #
20
+ # @param status [Symbol] Corresponding result status.
21
+ # @param contract_block [Proc] This block will be passed to {Contract.make} to get a contract.
22
+ def success_with(status, &contract_block)
23
+ success_contracts[status] = Flows::Contract.make(&contract_block)
24
+ end
25
+
26
+ # Defines a contract for a failure result with specific status.
27
+ #
28
+ # @param status [Symbol] Corresponding result status.
29
+ # @param contract_block [Proc] This block will be passed to {Contract.make} to get a contract.
30
+ def failure_with(status, &contract_block)
31
+ failure_contracts[status] = Flows::Contract.make(&contract_block)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ module Flows
2
+ module Plugin
3
+ module OutputContract
4
+ # Base error class for output contract errors.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when no single contract for successful results is defined
8
+ class NoContractError < Error
9
+ def initialize(klass)
10
+ @klass = klass
11
+ end
12
+
13
+ def message
14
+ "No single success contract defined for #{@klass}"
15
+ end
16
+ end
17
+
18
+ # Raised when result's data violates contract
19
+ class ContractError < Error
20
+ def initialize(klass, result, error)
21
+ @klass = klass
22
+ @result = result
23
+ @error = error
24
+ end
25
+
26
+ def message
27
+ shifted_error = @error.split("\n").map { |str| ' ' + str }.join("\n")
28
+
29
+ "Output contract for #{@klass} is violated.\n" \
30
+ "Result:\n" \
31
+ " `#{@result.inspect}`\n" \
32
+ "Contract Error:\n" \
33
+ "#{shifted_error}"
34
+ end
35
+ end
36
+
37
+ # Raised when no contract found for result
38
+ class StatusError < Error
39
+ def initialize(klass, result, allowed_statuses)
40
+ @klass = klass
41
+ @result = result
42
+ @allowed_statuses = allowed_statuses
43
+ end
44
+
45
+ def message
46
+ allowed_statuses_str = @allowed_statuses.map { |st| "`#{st.inspect}`" }.join(', ')
47
+
48
+ "Output contract for #{@klass} is violated.\n" \
49
+ "Result:\n" \
50
+ " `#{@result.inspect}`\n" \
51
+ "Contract Error:\n" \
52
+ " has unexpected status `#{@result.status.inspect}`\n" \
53
+ " allowed statuses for `#{@result.class}` are: #{allowed_statuses_str}"
54
+ end
55
+ end
56
+
57
+ # Raised when not a result object returned
58
+ class ResultTypeError < Error
59
+ def initialize(klass, result)
60
+ @klass = klass
61
+ @result = result
62
+ end
63
+
64
+ def message
65
+ "Output contract for #{@klass} is violated.\n" \
66
+ "Result:\n" \
67
+ " `#{@result.inspect}`\n" \
68
+ "Contract Error:\n" \
69
+ ' result must be instance of `Flows::Result`'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,53 @@
1
+ module Flows
2
+ module Plugin
3
+ module OutputContract
4
+ # Contains wrappers for initializer and `#call` methods.
5
+ #
6
+ # @api private
7
+ module Wrapper
8
+ def initialize(*args, &block)
9
+ super(*args, &block)
10
+ klass = self.class
11
+ raise NoContractError, klass if klass.success_contracts.empty?
12
+ end
13
+
14
+ def call(*args, &block)
15
+ result = super(*args, &block)
16
+ klass = self.class
17
+
18
+ contract = Util.contract_for(klass, result)
19
+
20
+ Util.transform_result(klass, contract, result)
21
+
22
+ result
23
+ end
24
+
25
+ # Helper methods for {Wrapper} are extracted to this
26
+ # module as singleton methods to not pollute user classes.
27
+ #
28
+ # @api private
29
+ module Util
30
+ class << self
31
+ def contract_for(klass, result)
32
+ raise ResultTypeError.new(klass, result) unless result.is_a?(Flows::Result)
33
+
34
+ status = result.status
35
+ contracts = result.ok? ? klass.success_contracts : klass.failure_contracts
36
+
37
+ contracts[status] || raise(StatusError.new(klass, result, contracts.keys))
38
+ end
39
+
40
+ def transform_result(klass, contract, result)
41
+ data = result.send(:data)
42
+
43
+ transformed_result = contract.transform(data)
44
+ raise ContractError.new(klass, result, transformed_result.error) if transformed_result.err?
45
+
46
+ result.send(:'data=', transformed_result.unwrap)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,51 +1,154 @@
1
- require_relative './railway/errors'
2
-
3
- require_relative './railway/dsl'
4
- require_relative './railway/builder'
5
- require_relative './railway/executor'
6
-
7
- require_relative './implicit_build'
1
+ require_relative 'railway/errors'
2
+ require_relative 'railway/step'
3
+ require_relative 'railway/step_list'
4
+ require_relative 'railway/dsl'
8
5
 
9
6
  module Flows
10
- # Railway DSL
11
- module Railway
12
- def self.included(mod)
13
- mod.extend ::Flows::Railway::DSL
14
- mod.extend ::Flows::ImplicitBuild
15
- end
7
+ # Flows::Railway is an implementation of a Railway Programming pattern.
8
+ #
9
+ # You may read about this pattern in the following articles:
10
+ #
11
+ # * [Programming on rails: Railway Oriented Programming](http://sandordargo.com/blog/2017/09/27/railway_oriented_programming).
12
+ # It's not about Ruby on Rails.
13
+ # * [Railway Oriented Programming: A powerful Functional Programming pattern](https://medium.com/@naveenkumarmuguda/railway-oriented-programming-a-powerful-functional-programming-pattern-ab454e467f31)
14
+ # * [Railway Oriented Programming in Elixir with Pattern Matching on Function Level and Pipelining](https://medium.com/elixirlabs/railway-oriented-programming-in-elixir-with-pattern-matching-on-function-level-and-pipelining-e53972cede98)
15
+ #
16
+ # Let's review a simple task and solve it using {Flows::Railway}:
17
+ #
18
+ # * you have to get a user by ID
19
+ # * get all user's blog posts
20
+ # * and convert it to an array of HTML-strings
21
+ #
22
+ # In such situation, we have to implement three parts of our task and compose it into something we can call,
23
+ # for example, from a Rails controller.
24
+ # Also, the first and third steps may fail (user not found, conversion to HTML failed).
25
+ # And if a step failed - we have to return failure info immediately.
26
+ #
27
+ # class RenderUserBlogPosts < Flows::Railway
28
+ # step :fetch_user
29
+ # step :get_blog_posts
30
+ # step :convert_to_html
31
+ #
32
+ # def fetch_user(id:)
33
+ # user = User.find_by_id(id)
34
+ # user ? ok(user: user) : err(message: "User #{id} not found")
35
+ # end
36
+ #
37
+ # def get_blog_posts(user:)
38
+ # ok(posts: User.posts)
39
+ # end
40
+ #
41
+ # def convert_to_html(posts:)
42
+ # posts_html = post.map(&:text).map do |text|
43
+ # html = convert(text)
44
+ # return err(message: "cannot convert to html: #{text}")
45
+ # end
46
+ #
47
+ # ok(posts_html: posts_html)
48
+ # end
49
+ #
50
+ # private
51
+ #
52
+ # # returns String or nil
53
+ # def convert(text)
54
+ # # some implementation here
55
+ # end
56
+ # end
57
+ #
58
+ # RenderUserBlogPosts.call(id: 10)
59
+ # # result object returned
60
+ #
61
+ # Let's describe how it works.
62
+ #
63
+ # First of all you have to inherit your railway from `Flows::Railway`.
64
+ #
65
+ # Then you must define list of your steps using `step` DSL method.
66
+ # Steps will be executed in the given order.
67
+ #
68
+ # The you have to provide step implementations. It should be done by using
69
+ # public methods with the corresponding names.
70
+ # _Please write your step implementations in the step definition order._
71
+ # _It will make your railway easier to read by other engineers._
72
+ #
73
+ # Each step should return {Flows::Result} Object.
74
+ # If Result Object is successful - next step will be called or
75
+ # this object becomes a railway execution result in the case of last step.
76
+ # If Result Object is failure - this object becomes execution result immediately.
77
+ #
78
+ # Place all the helpers methods in the private section of the class.
79
+ #
80
+ # To help with writing methods {Flows::Result::Helpers} is already included.
81
+ #
82
+ # {Railway} is a very simple but not very flexible abstraction.
83
+ # It has a good performance and a small overhead.
84
+ #
85
+ # ## `Flows::Railway` execution rules
86
+ #
87
+ # * steps execution happens from the first to the last step
88
+ # * input arguments (`Railway#call(...)`) becomes the input of the first step
89
+ # * each step should return Result Object (`Flows::Result::Helpers` already included)
90
+ # * if step returns failed result - execution stops and failed Result Object returned from Railway
91
+ # * if step returns successful result - result data becomes arguments of the following step
92
+ # * if the last step returns successful result - it becomes a result of a Railway execution
93
+ #
94
+ # ## Step definitions
95
+ #
96
+ # Two ways of step definition exist. First is by using an instance method:
97
+ #
98
+ # step :do_something
99
+ #
100
+ # def do_something(**arguments)
101
+ # # some implementation
102
+ # # Result Object as return value
103
+ # end
104
+ #
105
+ # Second is by using lambda:
106
+ #
107
+ # step :do_something, ->(**arguments) { ok(some: 'data') }
108
+ #
109
+ # Definition with lambda exists for debugging/testing purposes, it has higher priority than method implementation.
110
+ # _Do not use lambda implementations for your business logic!_
111
+ #
112
+ # __Think about Railway as about small book: you have a "table of contents"
113
+ # in a form of step definitions and actual "chapters" in the same order
114
+ # in a form of public methods. And your private methods becomes something like "appendix".__
115
+ #
116
+ # ## Advanced initialization
117
+ #
118
+ # In a simple case you can just invoke `YourRailway.call(..)`. Under the hood it works like `.new.call(...)`,
119
+ # but `.new` part will be executed ones and memoized ({Flows::Plugin::ImplicitInit} included).
120
+ #
121
+ # You can include {Flows::Plugin::DependencyInjector} into your Railway and in this case you will
122
+ # need to do `.new(...).call` manually.
123
+ class Railway
124
+ extend ::Flows::Plugin::ImplicitInit
16
125
 
17
126
  include ::Flows::Result::Helpers
127
+ extend ::Flows::Result::Helpers
18
128
 
19
- def initialize(method_source: nil, deps: {})
20
- _flows_do_checks
129
+ extend DSL
21
130
 
22
- flow = _flows_make_flow(method_source || self, deps)
23
- @_flows_executor = _flows_make_executor(flow)
24
- end
131
+ def initialize
132
+ klass = self.class
133
+ steps = klass.steps
25
134
 
26
- def call(**params)
27
- @_flows_executor.call(**params)
28
- end
135
+ raise NoStepsError, klass if steps.empty?
29
136
 
30
- private
31
-
32
- def _flows_do_checks
33
- raise NoStepsError if self.class.steps.empty?
137
+ @__flows_railway_flow = Flows::Flow.new(
138
+ start_node: steps.first_step_name,
139
+ node_map: steps.to_node_map(self)
140
+ )
34
141
  end
35
142
 
36
- def _flows_make_flow(method_source, deps)
37
- ::Flows::Railway::Builder.new(
38
- steps: self.class.steps,
39
- method_source: method_source,
40
- deps: deps
41
- ).call
42
- end
143
+ # Executes Railway with provided keyword arguments, returns Result Object
144
+ #
145
+ # @return [Flows::Result]
146
+ def call(**kwargs)
147
+ context = {}
43
148
 
44
- def _flows_make_executor(flow)
45
- ::Flows::Railway::Executor.new(
46
- flow: flow,
47
- class_name: self.class.name
48
- )
149
+ @__flows_railway_flow.call(ok(**kwargs), context: context).tap do |result|
150
+ result.meta[:last_step] = context[:last_step]
151
+ end
49
152
  end
50
153
  end
51
154
  end
@@ -1,27 +1,16 @@
1
1
  module Flows
2
- module Railway
3
- # DSL methods for Railway
2
+ class Railway
3
+ # @api private
4
4
  module DSL
5
5
  attr_reader :steps
6
6
 
7
- def self.extended(mod, steps = nil)
8
- mod.instance_variable_set(:@steps, steps || [])
7
+ Flows::Util::InheritableSingletonVars::DupStrategy.call(
8
+ self,
9
+ '@steps' => StepList.new
10
+ )
9
11
 
10
- mod.class_exec do
11
- def self.inherited(subclass)
12
- ::Flows::Railway::DSL.extended(subclass, steps.map(&:dup))
13
- super
14
- end
15
- end
16
- end
17
-
18
- include Flows::Result::Helpers
19
-
20
- def step(name, custom_body = nil)
21
- @steps << {
22
- name: name,
23
- custom_body: custom_body
24
- }
12
+ def step(name, lambda = nil)
13
+ steps.add(name: name, lambda: lambda)
25
14
  end
26
15
  end
27
16
  end
@@ -1,21 +1,17 @@
1
1
  module Flows
2
- module Railway
3
- # rubocop:disable Style/Documentation
4
- class NoStepsError < ::Flows::Error
5
- def message
6
- 'No steps defined'
7
- end
8
- end
2
+ class Railway
3
+ # Base class for Railway errors
4
+ class Error < StandardError; end
9
5
 
10
- class NoStepImplementationError < ::Flows::Error
11
- def initialize(step_name)
12
- @step_name = step_name
6
+ # Raised when initializing Railway with no steps
7
+ class NoStepsError < Error
8
+ def initialize(klass)
9
+ @klass = klass
13
10
  end
14
11
 
15
12
  def message
16
- "Missing step definition: #{@step_name}"
13
+ "No steps defined for #{@klass}"
17
14
  end
18
15
  end
19
- # rubocop:enable Style/Documentation
20
16
  end
21
17
  end
@@ -0,0 +1,24 @@
1
+ module Flows
2
+ class Railway
3
+ # @api private
4
+ Step = Struct.new(:name, :lambda, :next_step, keyword_init: true) do
5
+ NODE_PREPROCESSOR = ->(input, _, _) { [[], input.unwrap] }
6
+
7
+ NODE_POSTPROCESSOR = lambda do |output, context, meta|
8
+ context[:last_step] = meta[:name]
9
+
10
+ output
11
+ end
12
+
13
+ def to_node(method_source)
14
+ Flows::Flow::Node.new(
15
+ body: lambda || method_source.method(name),
16
+ router: Flows::Flow::Router::Simple.new(next_step || :end, :end),
17
+ meta: { name: name },
18
+ preprocessor: NODE_PREPROCESSOR,
19
+ postprocessor: NODE_POSTPROCESSOR
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end