Module: Statecraft::Machine::ClassMethods

Defined in:
lib/statecraft/machine.rb

Overview

Class-level DSL collected declaratively and compiled by finalize!. Names arrive as symbols or strings interchangeably (the ActiveRecord idiom) and are normalized to symbols at the declaration line.

Instance Method Summary collapse

Instance Method Details

#compiled_graphObject



150
151
152
# File 'lib/statecraft/machine.rb', line 150

def compiled_graph
  finalize!
end

#declared_callbacksObject



177
178
179
# File 'lib/statecraft/machine.rb', line 177

def declared_callbacks
  @statecraft_declared_callbacks ||= CALLBACK_PHASES.to_h { |phase| [phase, []] }
end

#declared_edgesObject



169
170
171
# File 'lib/statecraft/machine.rb', line 169

def declared_edges
  @statecraft_declared_edges ||= []
end

#declared_event_namesObject



173
174
175
# File 'lib/statecraft/machine.rb', line 173

def declared_event_names
  @statecraft_declared_event_names ||= []
end

#declared_statesObject



165
166
167
# File 'lib/statecraft/machine.rb', line 165

def declared_states
  @statecraft_declared_states ||= []
end

#event(name, from: nil, to: nil, guard: nil, record_guard: nil, input_guard: nil, lock: false, &declarations) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/statecraft/machine.rb', line 75

def event(name, from: nil, to: nil, guard: nil, record_guard: nil, input_guard: nil, lock: false, &declarations)
  name = name.to_sym
  declared_event_names << name
  if declarations
    if from || to
      raise CompilationError, "event #{name.inspect} takes either inline from:/to: or a block, not both"
    end

    begin
      @statecraft_current_event = { name: name, lock: lock }
      yield
    ensure
      @statecraft_current_event = nil
    end
  else
    raise CompilationError, "event #{name.inspect} needs from:/to: or a block" if from.nil? || to.nil?

    @statecraft_current_event = { name: name, lock: false }
    begin
      transition(from: from, to: to, guard: guard, record_guard: record_guard,
                 input_guard: input_guard, lock: lock)
    ensure
      @statecraft_current_event = nil
    end
  end
end

#eventsObject



130
131
132
# File 'lib/statecraft/machine.rb', line 130

def events
  compiled_graph.events.keys
end

#finalize!Object

Compilation memoizes the graph and freezes the declaration lists, so a reopened machine class fails loudly on any late state/transition/event instead of silently ignoring it (callbacks already freeze in compile).



161
162
163
# File 'lib/statecraft/machine.rb', line 161

def finalize!
  @statecraft_compiled_graph ||= Compiler.new(self).compile.tap { freeze_declarations }
end

#finalized?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/statecraft/machine.rb', line 154

def finalized?
  !@statecraft_compiled_graph.nil?
end

#initial_stateObject



146
147
148
# File 'lib/statecraft/machine.rb', line 146

def initial_state
  compiled_graph.initial_state
end

#state(name, initial: false) ⇒ Object



61
62
63
# File 'lib/statecraft/machine.rb', line 61

def state(name, initial: false)
  declared_states << { name: name.to_sym, initial: initial }
end

#statesObject



126
127
128
# File 'lib/statecraft/machine.rb', line 126

def states
  compiled_graph.states
end

#strict!Object

Opt-in shape strictness: with strict! the compiler additionally requires every declared state to be reachable from the initial one. Off by default — the column is written by more than the gem, so an unconnected state is legal unless the machine claims a closed graph.



120
121
122
# File 'lib/statecraft/machine.rb', line 120

def strict!
  @statecraft_strict = true
end

#strict?Boolean

Returns:

  • (Boolean)


124
# File 'lib/statecraft/machine.rb', line 124

def strict? = @statecraft_strict == true

#transition(from:, to:, guard: nil, record_guard: nil, input_guard: nil, lock: false) ⇒ Object



65
66
67
68
69
70
71
72
73
# File 'lib/statecraft/machine.rb', line 65

def transition(from:, to:, guard: nil, record_guard: nil, input_guard: nil, lock: false)
  declared_edges << {
    from: from.to_sym, to: to.to_sym,
    guards: Array(guard), record_guards: Array(record_guard),
    input_guards: Array(input_guard),
    lock: lock || current_event_lock,
    event: current_event_name
  }
end

#transitions_from(state) ⇒ Object

The graph's shape from one state: a frozen descriptor per outgoing edge, events empty for a bare edge. Shape, not a prediction — no guards are consulted, and a state outside the graph honestly owns no edges. Class-level on purpose: the answer is a property of the graph, never of a record.



139
140
141
142
143
144
# File 'lib/statecraft/machine.rb', line 139

def transitions_from(state)
  normalized = state.to_sym
  compiled_graph.edges.filter_map do |(from, _to), edge|
    { to: edge.to, events: edge.event_names }.freeze if from == normalized
  end.freeze
end