flows 0.3.0 → 0.4.0

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/{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