Module: Hecks::Projections::Model::Deviations

Defined in:
lib/hecks/projections/model/deviations.rb

Overview

HOW THE MODEL'S SHAPE DIFFERS FROM THE LANGUAGE'S, and why.

Every construct's emits_ir restates what the grammar declares — and the two legitimately differ, in seven ways. These lived only as prose in Ruby comments until spec/model_shape_conformance_spec made them checkable; they live HERE so the generator and the gate read one source rather than two that must agree.

A reason is carried beside each entry because the generated file will CARRY it: a deviation is exactly the kind of thing whose explanation must survive regeneration, and the only way it can is to be emitted rather than typed into the output.

Constant Summary collapse

PARENT_REF =

The grammar is relational — a Command points UP at its Aggregate — where the model composes. An explicit as: still keeps its _id (Command's own entity_id, kept as data); the parent link itself mints bare now (ADR 0025) — aggregate or bluebook, whichever this category's creating command declares first — so both spellings are checked. Entity spells its own (separate, non-colliding) text twin of the parent link owner.

->(field) { field.to_s.match?(/_id\z/) || %i[owner aggregate bluebook].include?(field) }
JUDGE_ONLY =

The judge's own field, never the model's — contracts.rb already says so with derived: { position: :walk }.

%i[position].freeze
CONTAINED =

What the model holds that the grammar declares elsewhere: the containment edges, stated in syntax.bluebook's Keyword rows as context -> opens.

{
  "Bluebook"       => %i[aggregates read_models policies process_managers],
  "Aggregate"      => %i[commands entities queries value_objects],
  # S17, ADR 0026 — an entity may nest further entities now
  # (`Dispatch`, inside `Handler`) — same containment edge as
  # Aggregate's own `entities`, one level down.
  "Entity"         => %i[commands entities queries],
  "ValueObject"    => %i[members],
  "ProcessManager" => %i[handlers]
}.freeze
FOLDED =

One model field gathered from several declared ones — contracts.rb's own [:folded, ...] shape, as the pair it is.

{
  "Aggregate" => { lifecycle: %i[state_field state_start transitions] },
  "Entity"    => { lifecycle: %i[state_field state_start transitions] },
  "Query"     => { order_by: %i[order_field order_way] }
}.freeze
UNPACKED =

The inverse of a fold, and it had no name at all: one declared field opening into several the model holds apart.

{
  "ReadModel" => { options: %i[wheres order_by limit] }
}.freeze
COMPUTED =

Model-only, each for its own reason rather than by oversight.

{
  "Bluebook"    => { ir_version:     "the EMISSION's own version, not the domain's",
                     canonical_form: "the normalisation table every reader needs beside the IR" },
  "ValueObject" => { closed_set:     "an empty one_of and no one_of are otherwise indistinguishable" },
  "Aggregate"   => { ports:          "declared in the hecksagon, attached after the aggregate exists" }
}.freeze
OFF_THE_WIRE =

DECLARED, AND DELIBERATELY NOT EMITTED. The category that had no home anywhere before — each of these was a comment and nothing more.

{
  "Policy"      => { aggregate: "the wire format is a pinned contract, and it does not carry " \
                                "where a policy was written before the builder hoisted it" },
  "Bluebook"    => { formerly_known_as: "a rename's old name is a fact about the source",
                     normalisations:    "the normalisation table rides on canonical_form instead" },
  "ValueObject" => { rows: "the language's name for a closed set's members; emitted as `members`" }
}.freeze
DYNAMIC_TAIL =

Emitted by to_h's own merge rather than by emits_ir — the two constructs whose shape is genuinely not fixed, because the query specification layer grew options after them.

{
  "Query"     => %i[options],
  "ReadModel" => %i[options group_by aggregate_heads count median_field]
}.freeze

Class Method Summary collapse

Class Method Details

.computed(name) ⇒ Object



90
# File 'lib/hecks/projections/model/deviations.rb', line 90

def computed(name)     = COMPUTED.fetch(name, {}).keys

.contained(name) ⇒ Object



91
# File 'lib/hecks/projections/model/deviations.rb', line 91

def contained(name)    = CONTAINED.fetch(name, [])

.dynamic_tail(name) ⇒ Object



94
# File 'lib/hecks/projections/model/deviations.rb', line 94

def dynamic_tail(name) = DYNAMIC_TAIL.fetch(name, [])

.folded(name) ⇒ Object



92
# File 'lib/hecks/projections/model/deviations.rb', line 92

def folded(name)       = FOLDED.fetch(name, {})

.off_the_wire(name) ⇒ Object

The tables that carry a reason answer with names only when the caller wants the set rather than the explanations.



89
# File 'lib/hecks/projections/model/deviations.rb', line 89

def off_the_wire(name) = OFF_THE_WIRE.fetch(name, {}).keys

.unpacked(name) ⇒ Object



93
# File 'lib/hecks/projections/model/deviations.rb', line 93

def unpacked(name)     = UNPACKED.fetch(name, {})