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



151
152
153
154
155
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 151

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

Instance Method Details

#buildObject



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 138

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

#ends_on_impl(event_ref) ⇒ Object

ends_on — same reasoning as starts_on_impl, above.



32
33
34
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 32

def ends_on_impl(event_ref)
  @ends_on = Naming.event_name_ref(event_ref)
end

#starts_on_impl(event_ref) ⇒ Object

starts_on Transfer::TransferRequested — BARE CONSTANT ACCEPTED (ADR 0025, S6 — "events first-class"), resolved through ConstShim the same way on_impl/transition_impl resolve an event reference — but through Naming. event_name_ref, NOT Naming.event_ref (that method's own header has the full account: SagaInterpreter matches pm.starts_on/pm.ends_on against a BARE event.name, never a "." qualified one). A plain String still passes through unchanged, both for shadow_parse and for any corpus site a future pass hasn't migrated yet.



27
28
29
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 27

def starts_on_impl(event_ref)
  @starts_on = Naming.event_name_ref(event_ref)
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).



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
130
131
132
133
134
135
136
# File 'lib/hecks/bluebook/dsl/process_manager_builder.rb', line 89

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|
    # BARE CONSTANT ACCEPTED (ADR 0025, S6 — "events first-
    # class"), `transition Account::AccountDebited => "state"` —
    # `Naming.event_name_ref`, NOT the DOTTED `Naming.event_ref`
    # transform `PolicyBuilder#on_impl` uses (that method's own
    # header has the full account, found live wiring a real
    # migrated corpus site into `bin/model_check` for the first
    # time: `SagaInterpreter#advance_saga` matches `handler.
    # event_type` against a BARE `event.name`, never a "."
    # qualified one — a policy's own cross-aggregate match
    # works differently, splitting the qualifier apart from the
    # name rather than comparing the whole string). Writing the
    # qualifier is still worth it (the same provenance `trigger
    # Account::Debit` gives a reader) — `event_name_ref` keeps
    # only the final segment, so `Account::AccountDebited` and a
    # bare `AccountDebited` store identically. A plain String
    # still passes through unchanged, both for `shadow_parse`
    # and for every corpus site this pass didn't migrate.
    state_transition = StateTransition.new(target: target, from: from)
    expand(Naming.event_name_ref(event_type), state_transition, handler.dispatches).each { |row| @handlers << row }
  end
end