Class: Flows::Flow

Inherits:
Object
  • Object
show all
Defined in:
lib/flows/flow.rb,
lib/flows/flow/node.rb,
lib/flows/flow/errors.rb,
lib/flows/flow/router.rb,
lib/flows/flow/router/custom.rb,
lib/flows/flow/router/errors.rb,
lib/flows/flow/router/simple.rb

Overview

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.

Abstraction for build deterministic finite-state machine-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, 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 Nodes. 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.

Examples:

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

Since:

  • 0.4.0

Defined Under Namespace

Classes: Error, InvalidFirstNodeError, InvalidNodeRouteError, Node, Router

Instance Method Summary collapse

Constructor Details

#initialize(start_node:, node_map:) ⇒ Flow

Returns a new instance of Flow.

Parameters:

  • start_node (Symbol)

    name of the entry node.

  • node_map (Hash<Symbol, Node>)

    keys are node names, values are nodes.

Raises:

Since:

  • 0.4.0



72
73
74
75
76
77
# File 'lib/flows/flow.rb', line 72

def initialize(start_node:, node_map:)
  @start_node = start_node
  @node_map = node_map

  check_routing_integrity
end

Instance Method Details

#call(input, context:) ⇒ Object

Executes a flow.

Parameters:

  • input (Object)

    initial input

  • context (Hash)

    mutable execution context

Returns:

  • (Object)

    execution result

Since:

  • 0.4.0



84
85
86
87
88
89
90
91
92
# File 'lib/flows/flow.rb', line 84

def call(input, context:)
  current_node_name = @start_node

  while current_node_name != :end
    input, current_node_name = @node_map[current_node_name].call(input, context: context)
  end

  input
end