Module: Hecks::Runtime::ReferenceHop
- Defined in:
- lib/hecks/runtime/reference_hop.rb
Overview
TenantScope's sibling — a hop folds to a synthetic in
where-clause the exact same way a tenant boundary folds to a
synthetic eq one, so every engine that already reads .wheres
answers a hop for free, with no per-engine code and no way for
one engine to forget it.
A hop's own filtering never happens here — RESOLVING one hop
means running one ordinary, adapter-agnostic query against the
hop's TARGET aggregate (through the same Ports::Query boundary
any other query goes through), and folding the ids it answers
back in as a local membership check. A multi-hop chain resolves
from the FAR END inward: fold only ever peels off the head hop
(QuerySpecification::HopPath.next_hop, the one-step primitive),
and hands everything still left in the tail to a recursive
apply call — so hop 2, hop 3, and so on each get resolved by
their own ordinary query against their own target, one recursion
level at a time, never by this module trying to see the whole
chain at once. It terminates because each recursion is over a
strictly shorter dotted string, and
BluebookBuilder#validate_query_hops! already refused any chain
deep enough to matter before this ever runs.
Defined Under Namespace
Classes: Folded
Class Method Summary collapse
- .apply(declared, args, registry:, domain:, aggregate:) ⇒ Object
- .fold(clause, args, registry:, domain:, aggregate:) ⇒ Object
-
.matching_ids(domain, target, wheres, args, registry:) ⇒ Object
Every id the inner clause admits on the hop's TARGET — one whole, ordinary query against the target's own repository, through the very same Ports::Query boundary the outer ask uses, so a hop is answered by whatever engine the target aggregate is actually bound to (which may not be the engine the OUTER aggregate is bound to at all) rather than by a second reading of the comparators.
Class Method Details
.apply(declared, args, registry:, domain:, aggregate:) ⇒ Object
34 35 36 37 38 39 40 |
# File 'lib/hecks/runtime/reference_hop.rb', line 34 def apply(declared, args, registry:, domain:, aggregate:) hopped, local = declared.wheres.partition { |clause| QuerySpecification::HopPath.hop_head?(clause.field, aggregate.attributes) } return declared if hopped.empty? folded = hopped.map { |clause| fold(clause, args, registry: registry, domain: domain, aggregate: aggregate) } Folded.new(declared, local + folded) end |
.fold(clause, args, registry:, domain:, aggregate:) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/hecks/runtime/reference_hop.rb', line 42 def fold(clause, args, registry:, domain:, aggregate:) step = QuerySpecification::HopPath.next_hop(clause.field, aggregate.attributes) hop, rest = step # A guarantee BluebookBuilder#validate_query_hops! already gave # this exact clause before the domain ever booted. Held anyway: # an unresolvable target folding silently to "matches # everything" is precisely the failure shape this whole feature # exists to close, and a hop that can no longer resolve (a # target aggregate unloaded after boot, say) is a real fact # worth a real error, not a query that quietly stops filtering. unless hop&.target raise WiringError, "#{clause.field} hops through a reference this domain cannot resolve " \ "right now — the chapter seal already checked this once; something " \ "changed between then and this dispatch" end inner = QuerySpecification::Common::WhereClause.new(field: rest, op: clause.op, value: clause.value) ids = matching_ids(domain, hop.target, [inner], args, registry: registry) QuerySpecification::Common::WhereClause.new(field: hop.attribute.name, op: "in", value: ids) end |
.matching_ids(domain, target, wheres, args, registry:) ⇒ Object
Every id the inner clause admits on the hop's TARGET — one whole, ordinary query against the target's own repository, through the very same Ports::Query boundary the outer ask uses, so a hop is answered by whatever engine the target aggregate is actually bound to (which may not be the engine the OUTER aggregate is bound to at all) rather than by a second reading of the comparators.
73 74 75 76 77 78 79 80 81 |
# File 'lib/hecks/runtime/reference_hop.rb', line 73 def matching_ids(domain, target, wheres, args, registry:) spec = apply(QuerySpecification::Common::Options.new(wheres: wheres), args, registry: registry, domain: domain, aggregate: target) repository = registry.repository(domain, target) rows = Ports::Query.execute(repository, spec, args, context: { domain: domain, aggregate: target }) || Ports::Query::InMemory.execute(repository.all, spec, args) rows.map { |row| row.id.to_s }.uniq end |