Class: Hecks::Bluebook::DSL::ProcessManagerBuilder

Inherits:
Object
  • Object
show all
Includes:
WordGate
Defined in:
lib/hecks/bluebook/dsl/process_manager_builder.rb

Defined Under Namespace

Classes: HandlerBuilder, InvalidProcessManager

Constant Summary collapse

GRAMMAR_CONTEXT =
"ProcessManager"

Constants included from WordGate

WordGate::NOT_ADMITTED

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ ProcessManagerBuilder

Returns a new instance of ProcessManagerBuilder.



12
13
14
15
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 12

def initialize(name)
  @name     = name
  @handlers = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Hecks::Bluebook::DSL::WordGate

Class Method Details

.build(name, &block) ⇒ Object



106
107
108
109
110
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 106

def self.build(name, &block)
  builder = new(name)
  builder.instance_eval(&block) if block
  builder.build
end

Instance Method Details

#buildObject



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 93

def build
  validate!

  ProcessManager.new(
    name:          @name,
    correlates_by: @correlates_by,
    starts_on:     @starts_on,
    ends_on:       @ends_on,
    states:        derived_states,
    handlers:      @handlers
  )
end

#transition_impl(mapping, &block) ⇒ Object

ONE STATE-MACHINE VOCABULARY (S7, ADR 0025 — "events and reactions"): the SAME word Lifecycle#transition already carries, one level over — transition "AccountDebited" => "awaiting_credit", from: "requested" do ... end replaces on "AccountDebited", transition: { "requested" => "awaiting_ credit" } do ... end. Same bare rocket-pair argument shape (not a NAMED transition: kwarg wrapping a second Hash), same from: — including the array form Lifecycle's own commands could already take and a process manager's own events could not — and the states a procedure runs on are DERIVED from the transitions that name them, the same way Behaviour::Lifecycle #states already derives an aggregate's ; state "x" lines duplicated exactly what the transition list already said, and could drift from it (validate!'s own "undeclared state" check existed only because they could).

starts_on/ends_on are NOT unified into this — verified against the real corpus rather than assumed: Settlement's own ends_on "TransferSettled" names an event NONE of its own transitions ever handle (Transfer.Settle's own emission, a full step downstream of the transition that dispatches it), so "the terminal state's own event" is not a fact the transition graph carries — deriving it would either be wrong for this exact corpus member or need a second new word to cover the case, which is not less vocabulary than keeping the one that already says it correctly.

EXPANDS IMMEDIATELY, unlike Lifecycle#transition (which defers to Behaviour::Lifecycle#expand, called at emission time) — ProcessManager's own IR constructor takes states:/ handlers: exactly as it always has, so the runtime (Behaviour::ProcessManager, SagaInterpreter, saga persistence/rehydration) needs no change at all: what changed is how the DECLARATION reaches that same shape, not the shape a real run ever sees or persists. RENAMED FROM transition — item #13's full metaprogrammed dispatch (slice 4c). Not bootstrap-reachable (checked directly — no core/attached chapter declares a ProcessManager of its own).



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 61

def transition_impl(mapping, &block)
  mapping = mapping.dup
  from    = mapping.delete(:from)

  # ALWAYS REQUIRED, unlike `Lifecycle#transition`'s own `from:`
  # — an aggregate's unconstrained transition is admitted from
  # ANY current state (`Behaviour::Lifecycle#applies_from?`
  # returns true for a nil `from`), a reading `SagaInterpreter#
  # advance_saga`'s own admission check does not share: it tests
  # `instance[:state] == handler.from_state` by plain equality,
  # nothing softer. Leaving that check unchanged (this slice's
  # own scope decision — see the class-level comment on why the
  # runtime stays untouched) means an unconstrained PM
  # transition would build cleanly and then match no instance
  # ever, silently — refused here instead, at the one point that
  # can still see the mistake.
  if from.nil?
    raise InvalidProcessManager,
          "#{@name}'s transition #{mapping.inspect} names no from: — a process manager's own " \
          "admission checks a saga instance's CURRENT state exactly, so a transition with no " \
          "from: would match no instance ever, silently"
  end

  handler = HandlerBuilder.new
  handler.instance_eval(&block) if block

  mapping.each do |event_type, target|
    state_transition = StateTransition.new(target: target, from: from)
    expand(event_type.to_s, state_transition, handler.dispatches).each { |row| @handlers << row }
  end
end