Module: Hecks::QuerySpecification::FieldPath
- Defined in:
- lib/hecks/query_specification/field_path.rb
Overview
ONE reading of a dotted query-field path, shared by every place that
used to invent its own. The reference interpreter did a bare
record[field] lookup (a dotted path silently matched nothing), the
SQL side split dots but judged numericness from the FIRST nested
segment only (a two-level path quietly compared text), and the build
seal walked the declaration graph a third way. Three
implementations of "what does pizza.price_cents.cents mean" is two
more than a language gets to have.
Two sides, deliberately in one file so they cannot drift:
dig(holder, field) — VALUE side: walk a record's held state,
segment by segment, through Value
objects and plain hashes alike.
leaf_attribute / numeric? — DECLARATION side: walk the declared
shape to the attribute a path lands on.
Callers supply value-object lookup as a
block, because an Aggregate and a
mid-build AggregateBuilder hold their
shapes differently but walk the same.
Constant Summary collapse
- NUMERIC_PRIMITIVES =
%w[Integer Float].freeze
- SCALAR_PRIMITIVES =
%w[String Integer Float TrueClass FalseClass].freeze
Class Method Summary collapse
-
.dig(holder, field) ⇒ Object
The held value a dotted field names, or nil — never a raise.
-
.leaf_attribute(attribute, segments) ⇒ Object
The declared attribute a path lands on, or nil.
-
.numeric?(attribute, segments, &value_object) ⇒ Boolean
May an ordered comparator (lt/lte/gt/gte) land here, and may an ORDER BY cast here numerically? A bare field keeps the one-level convention every adapter already implements: a numeric primitive, or a value object carrying at least one numeric member.
- .read(current, segment) ⇒ Object
-
.scalar_leaf?(attribute, segments, &value_object) ⇒ Boolean
A dotted path must end on a SCALAR member — landing on a value object would hand SQL a JSON object where the reference interpreter unwraps a hash, and the two would answer differently.
Class Method Details
.dig(holder, field) ⇒ Object
The held value a dotted field names, or nil — never a raise. The first segment reads off the record (an Instance, a Value, or a plain row hash); the rest read through whatever each step holds. A stored nested value object is a plain hash by the time it is read back, keyed by symbol in memory and by string off a wire decode, so both spellings are tried.
35 36 37 38 39 |
# File 'lib/hecks/query_specification/field_path.rb', line 35 def dig(holder, field) return nil if field.nil? field.to_s.split(".").reduce(holder) { |current, segment| read(current, segment) } end |
.leaf_attribute(attribute, segments) ⇒ Object
The declared attribute a path lands on, or nil. segments is the
dotted tail — for a bare field it is empty and the root attribute
is its own leaf. The walk stops dead at a reference (an id is a
scalar; nothing nests under it) and at any member the declared
value object does not carry.
53 54 55 56 57 58 59 60 61 62 |
# File 'lib/hecks/query_specification/field_path.rb', line 53 def leaf_attribute(attribute, segments) current = attribute segments.each do |segment| return nil if current.nil? || current.reference? shape = yield(current.type.to_s) current = shape&.attributes&.find { |member| member.name.to_s == segment } end current end |
.numeric?(attribute, segments, &value_object) ⇒ Boolean
May an ordered comparator (lt/lte/gt/gte) land here, and may an ORDER BY cast here numerically? A bare field keeps the one-level convention every adapter already implements: a numeric primitive, or a value object carrying at least one numeric member. A dotted path must land on a numeric primitive itself — the convention does not reach through a named path, it IS the absence of one.
70 71 72 73 74 75 76 77 78 |
# File 'lib/hecks/query_specification/field_path.rb', line 70 def numeric?(attribute, segments, &value_object) leaf = leaf_attribute(attribute, segments, &value_object) return false if leaf.nil? || leaf.list? || leaf.reference? return true if NUMERIC_PRIMITIVES.include?(leaf.type.to_s) return false unless segments.empty? shape = yield(leaf.type.to_s) !shape.nil? && shape.attributes.any? { |member| NUMERIC_PRIMITIVES.include?(member.type.to_s) } end |
.read(current, segment) ⇒ Object
41 42 43 44 45 46 |
# File 'lib/hecks/query_specification/field_path.rb', line 41 def read(current, segment) return nil if current.nil? return current[segment.to_sym] || current[segment] if current.is_a?(Hash) current[segment] end |
.scalar_leaf?(attribute, segments, &value_object) ⇒ Boolean
A dotted path must end on a SCALAR member — landing on a value object would hand SQL a JSON object where the reference interpreter unwraps a hash, and the two would answer differently.
83 84 85 86 |
# File 'lib/hecks/query_specification/field_path.rb', line 83 def scalar_leaf?(attribute, segments, &value_object) leaf = leaf_attribute(attribute, segments, &value_object) !leaf.nil? && !leaf.list? && !leaf.reference? && SCALAR_PRIMITIVES.include?(leaf.type.to_s) end |