Class: Flows::Flow::Node
- Inherits:
-
Object
- Object
- Flows::Flow::Node
- Defined in:
- lib/flows/flow/node.rb
Overview
Node is the main building block for Flows::Flow.
Node is an object which can be executed (#call) with some input and execution context and produce output and the next route.
Node execution consists of 4 sequential parts:
- Pre-processor execution (if pre-processor defined). Allows to modify input which will be transferred to the node's body. Allows to modify execution context. Has access to the node's metadata.
- Body execution. Body is a lambda which receives input and returns output.
- Post-processor execution (if post-processor defined). Allows to modify output which was produced by the node's body. Allows to modify execution context. Has access to the node's metadata.
- Router execution to determine next node name.
Execution result consists of 2 parts: output and next route.
Pre/postprocessors
Both have similar signatures:
preprocessor = lambda do |node_input, context, |
# returns input for the BODY
# format [args, kwargs]
end
postprocessor = lambda do |body_output, context, |
# returns output of the NODE
end
Types for body input and body_output
is under your control.
It allows you to adopt node for very different kinds of bodies.
Without pre-processor input
from #call becomes the first and single argument for the body.
In the cases when your body expects several arguments or keyword arguments
you must use pre-processor. Pre-processor returns array of 2 elements -
arguments and keyword arguments. There are some examples of a post-processor return
and the corresponding body call:
[[1, 2], {}]
body.call(1, 2)
[[], { a: 1, b: 2}]
body.call(a: 1, b: 2)
[[1, 2], { x: 3, y: 4 }]
body.call(1, 2, x: 3, y: 4)
[[1, 2, { 'a' => 3}], {}]
body.call(1, 2, 'a' => 3)
There were simpler solutions, but after this it's the most clear one.
context
and meta
are expected to be Ruby Hashes.
context
- is an execution context and it's mutable. Execution context is defined outside and not controlled by a node. Therefore, your mutations will be visible after a node execution.meta
- is node execution metadata and it's immutable, read only and node-local. Designed to store purely technical information like node name or, maybe, some dependency injection entities.
Metadata and node names
As you may see any Node instance has no name. It's done for the reason: name is not part of a node; it allows to use the same node instance under different names. In the most cases we don't need this, but it's nice to have such ability.
In some cases we want to have a node name inside pre/postprocessors.
For such cases meta
is the right place to store node name:
Flows::Flow::Node.new(body: body, router: router, meta: { name: :step_a })
Instance Attribute Summary collapse
-
#meta ⇒ Object
readonly
Node metadata, a frozen Ruby Hash.
- #router ⇒ Object readonly
Instance Method Summary collapse
-
#call(input, context:) ⇒ Array<(Object, Symbol)>
Executes the node.
-
#initialize(body:, router:, meta: {}, preprocessor: nil, postprocessor: nil) ⇒ Node
constructor
A new instance of Node.
Constructor Details
#initialize(body:, router:, meta: {}, preprocessor: nil, postprocessor: nil) ⇒ Node
Returns a new instance of Node.
92 93 94 95 96 97 98 99 100 |
# File 'lib/flows/flow/node.rb', line 92 def initialize(body:, router:, meta: {}, preprocessor: nil, postprocessor: nil) @body = body @router = router @meta = .freeze @preprocessor = preprocessor @postprocessor = postprocessor end |
Instance Attribute Details
#meta ⇒ Object (readonly)
Node metadata, a frozen Ruby Hash.
84 85 86 |
# File 'lib/flows/flow/node.rb', line 84 def @meta end |
#router ⇒ Object (readonly)
85 86 87 |
# File 'lib/flows/flow/node.rb', line 85 def router @router end |
Instance Method Details
#call(input, context:) ⇒ Array<(Object, Symbol)>
Executes the node.
:reek:TooManyStatements
is disabled for this method because even
one more call to a private method impacts performance here.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/flows/flow/node.rb', line 114 def call(input, context:) output = if @preprocessor args, kwargs = @preprocessor.call(input, context, @meta) # https://bugs.ruby-lang.org/issues/14415 kwargs.empty? ? @body.call(*args) : @body.call(*args, **kwargs) else @body.call(input) end output = @postprocessor.call(output, context, @meta) if @postprocessor route = @router.call(output) [output, route] end |