Class: Hecks::Runtime::EntityInterpreter

Inherits:
Object
  • Object
show all
Includes:
CommandInterpreter::ArgumentGate, Interpreting
Defined in:
lib/hecks/runtime/entity_interpreter.rb

Defined Under Namespace

Classes: Context

Constant Summary collapse

DISPATCH_ORDER =

THE DECLARED ORDER, HAND-TYPED — mirrors Vocabulary::EntityDispatchOrder (language/bluebook/vocabulary.bluebook:217-232), held equal to it by spec/vocabulary_conformance_spec.rb the same way CommandInterpreter's own DISPATCH_ORDER is; see that constant's doc comment for why this is hand-typed rather than read live off the meta-domain at every dispatch. refuse_unknown_arguments/refuse_absent_arguments now lead it, same position AggregateDispatchOrder holds them at (H1, above) — the only remaining difference from the aggregate order is no assign_creation_attributes (an entity is never created through this path).

Hecks::Vocabulary.symbols("EntityDispatchOrder")
MAX_STALE_WRITE_RETRIES =

Same safety valve as CommandInterpreter::MAX_STALE_WRITE_RETRIES — see that constant's own comment.

5

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Interpreting

included

Constructor Details

#initialize(registry, rules:) ⇒ EntityInterpreter

Returns a new instance of EntityInterpreter.



68
69
70
71
# File 'lib/hecks/runtime/entity_interpreter.rb', line 68

def initialize(registry, rules:)
  @registry = registry
  @rules    = rules
end

Instance Attribute Details

#registryObject (readonly)

Returns the value of attribute registry.



33
34
35
# File 'lib/hecks/runtime/entity_interpreter.rb', line 33

def registry
  @registry
end

Instance Method Details

#call(domain, aggregate, dotted, legacy_args, route: nil, with: nil, dry_run: false) ⇒ Object

dry_run: — CommandInterpreter#call's own twin, see that method's comment for the shared reasoning (Dispatcher#dry_run?'s own entry point). step_save/step_emit are the only two steps here that read it either. RETRIES THE WHOLE METHOD BODY on StaleWrite — same reasoning as CommandInterpreter#call's own retry: a fresh ctx, a fresh step_hydrate_parent/step_locate_element re-reading current state.



81
82
83
84
85
86
87
88
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
# File 'lib/hecks/runtime/entity_interpreter.rb', line 81

def call(domain, aggregate, dotted, legacy_args, route: nil, with: nil, dry_run: false)
  *entity_names, command_name = dotted.to_s.split(".")
  if entity_names.empty?
    raise UnknownVerb, RefusalWording.render("UnknownVerb", "entity_unknown",
                                             aggregate: aggregate.hecks_name, entity: dotted.to_s.inspect)
  end

  chain  = walk_entity_chain(aggregate, entity_names)
  entity = chain.last
  command = entity.command(command_name) ||
            raise(UnknownVerb, RefusalWording.render("UnknownVerb", "entity_no_command",
                                                     entity: entity.hecks_name, command: command_name.inspect))

  args = Routing.payload(command, with: with, legacy: legacy_args)
  attempt = 0
  begin
    ctx = Context.new(domain, aggregate, entity, entity_names.join("."), command, command_name, args)
    ctx.chain = chain
    ctx.route = route
    ctx.dry_run = dry_run
    # `root_aggregate:` — `entity` is the immediate owner (what
    # `owner_fields` inside the Analyzer means), but a `parent.X`
    # read inside this command's own given/ensures means the ROOT
    # aggregate's own field, not the entity's — `aggregate` here IS
    # that root (this method's own first parameter, never the
    # entity). See DependencyPlanning::Analyzer.call's own header
    # for the bug this closes.
    ctx.plan = DependencyPlanning::Analyzer.call(aggregate: entity, command: command, root_aggregate: aggregate)
    # RESOLVED HERE, ONCE — see CommandInterpreter#call's own comment;
    # `step_hydrate_parent` reads `ctx.repository` without re-fetching.
    ctx.repository = @registry.repository(domain, aggregate)
    lock_id = Identity.best_effort(aggregate, args, route)
    run_dispatch_order_with_isolation(DISPATCH_ORDER, ctx, lock_key_id: lock_id)
    [ctx.instance, ctx.result, ctx.plan, ctx.persistence_outcome]
  rescue StaleWrite
    attempt += 1
    retry if attempt < MAX_STALE_WRITE_RETRIES
    raise
  end
end