lib/flows/flow.rb in flows-0.3.0 vs lib/flows/flow.rb in flows-0.4.0
- old
+ new
@@ -1,19 +1,87 @@
+require_relative 'flow/node'
+require_relative 'flow/router'
+
module Flows
- # Simple sequential flow
+ # Abstraction for build [deterministic finite-state machine](https://www.freecodecamp.org/news/state-machines-basics-of-computer-science-d42855debc66/)-like
+ # execution objects.
+ #
+ # Let's refer to 'deterministic finite-state machine' as DFSM.
+ #
+ # It's NOT an implementation of DFSM. It just shares a lot of
+ # structural ideas. You can also think about {Flow} as an [oriented graph](https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Oriented_graph),
+ # where:
+ #
+ # * you have the one and only one initial node
+ # * you start execution from the initial node
+ # * after node execution your are going to some next node or stop execution.
+ #
+ # And edges formed by possible next nodes.
+ #
+ # DFSM has a very important property:
+ #
+ # > From any state, there is only one transition for any allowed input.
+ #
+ # So, we represent DFSM states as {Node}s. Each {Node}, basing on input (input includes execution context also)
+ # performs some side effects and returns output and next {Node} name (DFSM state).
+ #
+ # Side effects here can be spitted into two types:
+ #
+ # * modification of execution context
+ # * rest of them: working with 3rd party libraries, printing to STDOUT, etc.
+ #
+ # Final state represented by special symbol `:end`.
+ #
+ # @note You should not use {Flow} in your business code. It's designed to be underlying execution engine
+ # for high-level abstractions. In other words - it's for libraries, not applications.
+ #
+ # @example Calculates sum of elements in array. If sum more than 10 prints 'Big', otherwise prints 'Small'.
+ #
+ # flow = Flows::Flow.new(
+ # start_node: :sum_list,
+ # node_map: {
+ # sum_list: Flows::Flow::Node.new(
+ # body: ->(list) { list.sum },
+ # router: Flows::Flow::Router::Custom.new(
+ # ->(x) { x > 10 } => :print_big,
+ # ->(x) { x <= 10 } => :print_small
+ # )
+ # ),
+ # print_big: Flows::Flow::Node.new(
+ # body: ->(_) { puts 'Big' },
+ # router: Flows::Flow::Router::Custom.new(
+ # nil => :end # puts returns nil.
+ # )
+ # ),
+ # print_small: Flows::Flow::Node.new(
+ # body: ->(_) { puts 'Small' },
+ # router: Flows::Flow::Router::Custom.new(
+ # nil => :end # puts returns nil.
+ # )
+ # )
+ # }
+ # )
+ #
+ # flow.call([1, 2, 3, 4, 5], context: {})
+ # # prints 'Big' and returns nil
class Flow
- def initialize(start_node:, nodes:)
+ # @param start_node [Symbol] name of the entry node.
+ # @param node_map [Hash<Symbol, Node>] keys are node names, values are nodes.
+ def initialize(start_node:, node_map:)
@start_node = start_node
- @nodes = Hash[
- nodes.map { |node| [node.name, node] }
- ]
+ @node_map = node_map
end
+ # Executes a flow.
+ #
+ # @param input [Object] initial input
+ # @param context [Hash] mutable execution context
+ # @return [Object] execution result
def call(input, context:)
current_node_name = @start_node
- while current_node_name != :term
- input, current_node_name = @nodes[current_node_name].call(input, context: context)
+ while current_node_name != :end
+ input, current_node_name = @node_map[current_node_name].call(input, context: context)
end
input
end
end