Class: NucleusCore::Workflow

Inherits:
Object
  • Object
show all
Defined in:
lib/nucleus_core/workflow.rb

Defined Under Namespace

Classes: Node, Process

Constant Summary collapse

INITIAL_STATE =

States

:initial
CONTINUE =

Signals

:continue
WAIT =
:wait
OK =

Node statuses

:ok
FAILED =
:failed

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(process: nil, context: {}) ⇒ Workflow

Returns a new instance of Workflow.



47
48
49
50
51
52
53
# File 'lib/nucleus_core/workflow.rb', line 47

def initialize(process: nil, context: {})
  @nodes = {}
  @process = process || Process.new(INITIAL_STATE)
  @context = build_context(context)

  init_nodes
end

Instance Attribute Details

#contextObject

Returns the value of attribute context.



45
46
47
# File 'lib/nucleus_core/workflow.rb', line 45

def context
  @context
end

#nodesObject

Returns the value of attribute nodes.



45
46
47
# File 'lib/nucleus_core/workflow.rb', line 45

def nodes
  @nodes
end

#processObject

Returns the value of attribute process.



45
46
47
# File 'lib/nucleus_core/workflow.rb', line 45

def process
  @process
end

Class Method Details

.call(signal: nil, process: nil, context: {}) ⇒ Object



75
76
77
78
79
80
81
82
# File 'lib/nucleus_core/workflow.rb', line 75

def self.call(signal: nil, process: nil, context: {})
  workflow = new(process: process, context: context)

  workflow.validate_nodes!
  workflow.execute(signal)

  [workflow.context, workflow.process]
end

.rollback(process:, context:) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/nucleus_core/workflow.rb', line 84

def self.rollback(process:, context:)
  workflow = new(process: process, context: context)
  visited = workflow.process.visited.clone

  visited.reverse_each do |state|
    node = workflow.nodes[state]

    next node.operation.rollback(context) if node.operation.is_a?(NucleusCore::Operation)
    next node.rollback.call(context) if node.rollback.is_a?(Proc)
  end
end

Instance Method Details

#defineObject

Override this method to draw the workflow graph



72
73
# File 'lib/nucleus_core/workflow.rb', line 72

def define
end

#execute(signal = nil) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/nucleus_core/workflow.rb', line 105

def execute(signal=nil)
  signal ||= CONTINUE
  current_state = process.state
  next_signal = (fetch_node(current_state)&.signals || {})[signal]
  current_node = fetch_node(next_signal)

  context.fail!("invalid signal: #{signal}") if current_node.nil?

  while next_signal
    status, next_signal, @context = execute_node(current_node, context)

    break if status == FAILED

    process.visit(current_node.state)
    current_node = fetch_node(next_signal)

    break if next_signal == WAIT
  end

  context
rescue NucleusCore::Operation::Context::Error
  context
rescue StandardError => e
  fail_context(@context, e)
end

#init_nodesObject



67
68
69
# File 'lib/nucleus_core/workflow.rb', line 67

def init_nodes
  define
end

#register_node(attrs = {}) ⇒ Object



55
56
57
58
59
# File 'lib/nucleus_core/workflow.rb', line 55

def register_node(attrs={})
  node = Node.new(attrs)

  @nodes[node.state] = node
end

#start_node(signals = {}) ⇒ Object

Raises:

  • (ArgumentError)


61
62
63
64
65
# File 'lib/nucleus_core/workflow.rb', line 61

def start_node(signals={})
  raise ArgumentError, "#{self.class}##{__method__}: missing signals" if signals.empty?

  register_node(state: INITIAL_STATE, signals: signals)
end

#validate_nodes!Object

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
# File 'lib/nucleus_core/workflow.rb', line 96

def validate_nodes!
  start_nodes = nodes.values.count do |node|
    node.state == INITIAL_STATE
  end

  raise ArgumentError, "#{self.class}: missing `:initial` start node" if start_nodes.zero?
  raise ArgumentError, "#{self.class}: more than one start node detected" if start_nodes > 1
end