Class: Hecks::Bluebook::DSL::PolicyBuilder

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

Constant Summary collapse

GRAMMAR_CONTEXT =
"Policy"

Constants included from WordGate

WordGate::NOT_ADMITTED

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ PolicyBuilder

Returns a new instance of PolicyBuilder.



10
11
12
# File 'lib/hecks/bluebook/dsl/policy_builder.rb', line 10

def initialize(name)
  @name = name
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



112
113
114
115
116
# File 'lib/hecks/bluebook/dsl/policy_builder.rb', line 112

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

Instance Method Details

#buildObject

THE FAN-OUT SOURCE — a query verb, "Aggregate.query_name" or "Domain::Aggregate.query_name", the same qualified-or-not shape a saga's own dispatch command name already takes (SagaInterpreter#qualified). Runtime::PolicyInterpreter runs the named query against the triggering event's own payload and fires trigger once per row, instead of once for the event. for_each — item #13's full metaprogrammed dispatch, slice 1: same shape as on, above.



100
101
102
103
104
105
106
107
108
109
110
# File 'lib/hecks/bluebook/dsl/policy_builder.rb', line 100

def build
  Policy.new(
    name:            @name,
    on_event:        @on_event,
    trigger_command: @trigger_command,
    target_domain:   @target_domain,
    where:           @where,
    for_each:        @for_each,
    with_spec:       @with_spec || []
  ).tap { |policy| policy.instance_variable_set(:@projection_declared, !!@projection_declared) }
end

#trigger_impl(command_ref, with: nil) ⇒ Object

with: — WHAT THE TRIGGER IS GIVEN, when the event's own shape is not it. Omitted, the whole event payload forwards verbatim, which is what every policy did before this existed.

Same key => value shape a saga's own dispatch ..., with: takes, and read the same way at runtime: a Symbol names a field on the triggering event, anything else is a literal the policy supplies itself. The reason it exists is the reason a saga's does — a reaction crosses an aggregate boundary, and the event on one side is under no obligation to be shaped like the command on the other. Without it the target has to DECLARE every field the event happens to carry, whether it reads them or not. THE COMMAND ITSELF, NOT ITS NAME (ADR 0025, "events and reactions" — command references become first-class): trigger Account::Debit, a bare constant ConstShim resolves the same way reference_to Account always has, not a quoted verb string. Collapses the qualified/unqualified split this word and a saga's own dispatch used to disagree about — see Naming.command_ref's own header for how the ::/. rewrite works, and SagaInterpreter#qualified for why an unqualified form has always been enough (same-domain is the fallback, so Account::Debit and Banking::Account::Debit mean the same thing here).

LEGACY UNDER SHADOW-PARSING (S0a's own bridge) — frozen era text still writes the quoted form.

RENAMED FROM trigger — item #13's full metaprogrammed dispatch (slice 4), same reasoning as has_many_impl above: not bootstrap-reachable, reached through calls: with no fallback needed.



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/hecks/bluebook/dsl/policy_builder.rb', line 50

def trigger_impl(command_ref, with: nil)
  if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
    raise Malformed,
          "#{@name}'s trigger #{command_ref.inspect} is quoted text — give the bare command " \
          "constant instead, e.g. trigger Account::Debit"
  end

  @trigger_command = Naming.command_ref(command_ref)
  @with_spec = (with || {}).to_a
  @projection_declared = !with.nil?
end

#where(&predicate) ⇒ Object

THE GUARD — same extraction CommandBuilder#given/#ensures already use (Ports::Extraction reads the block's SOURCE ; the block itself is never called, here or at runtime — Runtime::PolicyInterpreter evaluates the extracted text through the same Bluebook::Expression::Evaluator a command's own given/ensures run through). No description argument the way given/ensures each carry one : a given's description becomes a GivenNotMet message, and a where that does not hold refuses nothing — it just means this policy does not apply to this event, exactly like an event_qualifier miss, which carries no message either.

Evaluated against the triggering EVENT's OWN PAYLOAD, not a stored record — a policy reacts to what just happened, and has no aggregate instance of its own to read state from.



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/hecks/bluebook/dsl/policy_builder.rb', line 79

def where(&predicate)
  canonical = Ports::Extraction.canonical(predicate)

  if canonical.to_s.empty?
    raise Malformed,
          "#{@name}'s where did not survive extraction — its source " \
          "could not be read, so no other runtime could ever evaluate it"
  end

  @where = canonical
end