Class: Hecks::Runtime::Value
- Inherits:
-
Object
- Object
- Hecks::Runtime::Value
- Defined in:
- lib/hecks/runtime/value.rb,
lib/hecks/runtime/value/coercion.rb,
lib/hecks/runtime/value/admission.rb
Overview
A typed value object in hand: frozen fields, read by name. How one is
MADE — coerced from a raw argument, checked against its declared
numeric types, patterns and closed sets — is the class-side engine in
value/coercion.rb and value/admission.rb, extended here so the door
stays where it always was: Value.for, Value.build.
Defined Under Namespace
Constant Summary
Constants included from Coercion
Coercion::COMPOSITE_SHAPES, Coercion::NON_NUMERIC_SCALARS, Coercion::NUMERIC, Coercion::SHAPES
Instance Attribute Summary collapse
-
#value_object ⇒ Object
readonly
Returns the value of attribute value_object.
Class Method Summary collapse
-
.latest_by(rows, key) ⇒ Object
REDUCES AN APPEND-ONLY SUB-LOG TO ITS CURRENT STATE — the same "a later fact supersedes an earlier one" reduction this runtime already performs replaying an AGGREGATE's own command history into its current attributes, applied here to a single
list_ofFIELD acting as its own miniature append-only log (a placement history, a tombstone-style soft-delete list, a versioned setting). - .materialize(value) ⇒ Object
-
.materialize_unwrapped(value) ⇒ Object
materialize, but a single-attribute value object (sole_attribute— [[feedback_name_the_scalar_field]]) recurses into its own bare field instead of building{field: ...}— a Board's ownlabelunwraps to"Kanban", not{value: "Kanban"}.
Instance Method Summary collapse
- #==(other) ⇒ Object
- #[](field) ⇒ Object
-
#initialize(value_object, fields) ⇒ Value
constructor
FROZEN THROUGH, not just on top.
- #key?(field) ⇒ Boolean
- #method_missing(name, *args) ⇒ Object
- #respond_to_missing?(name, include_private = false) ⇒ Boolean
- #to_h ⇒ Object
- #to_json ⇒ Object
- #type_name ⇒ Object
- #with(field, value) ⇒ Object
Methods included from Coercion
build, canonical_fields, fields_for, find_entity, for, for_attribute, from_identifier, hydrate, hydrate_entity_list, known_by, normalize_composite_fields, reference_identity, reference_list, refuse_object_reference, scalar, value_object_for
Methods included from Admission
admit_declared_set, admit_member, admitted_members, admitted_scalar, chapter_of
Constructor Details
#initialize(value_object, fields) ⇒ Value
FROZEN THROUGH, not just on top.
@fields.freeze alone stops a key being added or removed and
nothing else: the String, Array or Hash a field HOLDS stays
mutable, so vo[:value] << "!" edits a value object in place —
demonstrated on a real dispatch before this was written, not
supposed. Same shape as the three freezing bugs already fixed
(list attributes, the event log, query rows), where the container
was frozen and the contents were not.
A value object is the one thing in the domain that has no
identity to change over — with already answers a NEW one rather
than mutating — so freezing it through is what it always claimed
to be.
34 35 36 37 38 |
# File 'lib/hecks/runtime/value.rb', line 34 def initialize(value_object, fields) @value_object = value_object @fields = Freezer.deep(fields.transform_keys(&:to_sym)) freeze end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
114 115 116 117 118 |
# File 'lib/hecks/runtime/value.rb', line 114 def method_missing(name, *args) return @fields[name] if @fields.key?(name) super end |
Instance Attribute Details
#value_object ⇒ Object (readonly)
Returns the value of attribute value_object.
18 19 20 |
# File 'lib/hecks/runtime/value.rb', line 18 def value_object @value_object end |
Class Method Details
.latest_by(rows, key) ⇒ Object
REDUCES AN APPEND-ONLY SUB-LOG TO ITS CURRENT STATE — the same
"a later fact supersedes an earlier one" reduction this runtime
already performs replaying an AGGREGATE's own command history
into its current attributes, applied here to a single list_of
FIELD acting as its own miniature append-only log (a placement
history, a tombstone-style soft-delete list, a versioned
setting). rows is what a list_of attribute hands back — an
Array of Value, in append order — and key names the field
that identifies "the same logical thing" across entries.
GROUPING ONLY, NEVER INTERPRETATION. What counts as "removed," how to order what survives — that meaning belongs to whichever domain declared the field, never here: a generic reduction that started guessing domain semantics would need to keep guessing forever, once per shape of "gone" any caller ever invents. The caller filters and sorts the result; this only groups it.
110 111 112 |
# File 'lib/hecks/runtime/value.rb', line 110 def self.latest_by(rows, key) rows.each_with_object({}) { |row, latest| latest[row.public_send(key)] = row }.values end |
.materialize(value) ⇒ Object
54 55 56 57 58 59 60 61 |
# File 'lib/hecks/runtime/value.rb', line 54 def self.materialize(value) case value when self then value.to_h when Array then value.map { |item| materialize(item) } when Hash then value.transform_values { |item| materialize(item) } else value end end |
.materialize_unwrapped(value) ⇒ Object
materialize, but a single-attribute value object (sole_attribute
— [[feedback_name_the_scalar_field]]) recurses into its own bare
field instead of building {field: ...} — a Board's own label
unwraps to "Kanban", not {value: "Kanban"}. NOT a replacement
for materialize itself: every existing report/query/command
caller keeps the wrapped shape it already depends on (Banking's
own CustomerPortfolio reads payment[:amount][:cents], and
changing that out from under it would be a real breaking change,
not a bug fix). This is read_model_interpreter.rb's own opt-in,
used ONLY for a group_by-declared head's own rows — grouping
needs a real scalar to key by regardless, so a report already
asking for that gets the unwrap for free.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/hecks/runtime/value.rb', line 75 def self.materialize_unwrapped(value) case value when self sole = value.value_object.sole_attribute return materialize_unwrapped(value[sole.name]) if sole # NOT `value.to_h` — `Value#to_h` materializes each field through # plain `materialize`, so a VO nested inside a multi-attribute VO # would already be a plain Hash by the time this method ever saw # it, and never reach the `when self` branch above. Read each # field straight off `value` instead, so recursion actually # happens through THIS method the whole way down. value.value_object.attributes.to_h { |attr| [attr.name, materialize_unwrapped(value[attr.name])] } when Array then value.map { |item| materialize_unwrapped(item) } when Hash then value.transform_values { |item| materialize_unwrapped(item) } else value end end |
Instance Method Details
#==(other) ⇒ Object
46 47 48 |
# File 'lib/hecks/runtime/value.rb', line 46 def ==(other) other.is_a?(self.class) && other.type_name == type_name && other.to_h == to_h end |
#[](field) ⇒ Object
41 |
# File 'lib/hecks/runtime/value.rb', line 41 def [](field) = @fields[field.to_sym] |
#key?(field) ⇒ Boolean
42 |
# File 'lib/hecks/runtime/value.rb', line 42 def key?(field) = @fields.key?(field.to_sym) |
#respond_to_missing?(name, include_private = false) ⇒ Boolean
120 121 122 |
# File 'lib/hecks/runtime/value.rb', line 120 def respond_to_missing?(name, include_private = false) @fields.key?(name) || super end |
#to_h ⇒ Object
43 |
# File 'lib/hecks/runtime/value.rb', line 43 def to_h = @fields.transform_values { |value| self.class.materialize(value) } |
#to_json ⇒ Object
44 |
# File 'lib/hecks/runtime/value.rb', line 44 def to_json(*) = JSON.generate(to_h) |
#type_name ⇒ Object
40 |
# File 'lib/hecks/runtime/value.rb', line 40 def type_name = @value_object.hecks_name |
#with(field, value) ⇒ Object
50 51 52 |
# File 'lib/hecks/runtime/value.rb', line 50 def with(field, value) self.class.build(@value_object, @fields.merge(field.to_sym => value)) end |