Module: Hecks::Runtime::Value::Admission
- Included in:
- Hecks::Runtime::Value
- Defined in:
- lib/hecks/runtime/value/admission.rb
Overview
Closed sets — one_of members and admits: declarations — and the
refusals that make them rules rather than decoration. Extended into
Value beside Coercion; self is the Value class.
Instance Method Summary collapse
-
#admit_declared_set(owner, attribute, value) ⇒ Object
THE SAME REFUSAL, FOR A SET NAMED SOMEWHERE ELSE.
- #admit_member(value_object, fields) ⇒ Object
-
#admitted_members(owner, attribute) ⇒ Object
Vocabulary::MutationOp— the aggregate that HOLDS the set, then the set. -
#admitted_scalar(value) ⇒ Object
An admitted value is a SCALAR however it arrived — as a bare string on a plain field, or wrapped in the one-field holder its type names.
-
#chapter_of(construct) ⇒ Object
UP TO THE CHAPTER, from wherever the attribute was declared.
Instance Method Details
#admit_declared_set(owner, attribute, value) ⇒ Object
THE SAME REFUSAL, FOR A SET NAMED SOMEWHERE ELSE.
admit_member above refuses a non-member when the value object BEING
BUILT is itself the closed set — which is the only shape one_of can
make, because it SYNTHESISES the set from the values written inline. An
admits: attribute is the other direction: the value is an ordinary
String or a plain text holder, and the set it must belong to was
declared once, elsewhere, and is named rather than restated.
Without this the word was a DECLARATION and nothing more — read by projections, read by nobody at the door. A rule that cannot be the one to refuse is decoration, and this language has paid for that mistake before.
38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/hecks/runtime/value/admission.rb', line 38 def admit_declared_set(owner, attribute, value) return value if attribute.nil? || attribute.admits.nil? || value.nil? admitted = admitted_members(owner, attribute) offered = admitted_scalar(value) return value if admitted.include?(offered.to_s) raise InvariantViolation, RefusalWording.render("InvariantViolation", "admits_declared_set", name: attribute.name, admits: attribute.admits, admitted: admitted.map(&:inspect).join(", "), offered: offered.inspect) end |
#admit_member(value_object, fields) ⇒ Object
11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/hecks/runtime/value/admission.rb', line 11 def admit_member(value_object, fields) return if value_object.members.empty? discriminant = value_object.attributes.first.name offered = fields[discriminant] admitted = value_object.members.map { |member| member[discriminant].to_s } return if admitted.include?(offered.to_s) raise InvariantViolation, RefusalWording.render("InvariantViolation", "closed_set_member", type: value_object.hecks_name, admitted: admitted.map(&:inspect).join(", "), offered: offered.inspect) end |
#admitted_members(owner, attribute) ⇒ Object
Vocabulary::MutationOp — the aggregate that HOLDS the set, then the
set. Qualified because a closed set is a value object INSIDE an
aggregate, which is exactly why admits could not be spelled as a
reference: reference_to reaches aggregate heads and nothing below one.
RESOLVED LATE, like Reference#resolve and for the same reason — the
set may be declared further down the file than the attribute that names
it. And REFUSED when it resolves to nothing, also like Reference: a
link checked against nothing is worse than no link, because it reads
like a rule.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/hecks/runtime/value/admission.rb', line 61 def admitted_members(owner, attribute) aggregate_name, set_name = attribute.admits.to_s.split("::", 2) chapter = chapter_of(owner) set = set_name && chapter&.aggregate(aggregate_name)&.value_object(set_name) unless set raise InvariantViolation, RefusalWording.render("InvariantViolation", "undeclared_set", name: attribute.name, admits: attribute.admits) end discriminant = set.attributes.first.name set.members.map { |member| member.to_h[discriminant].to_s } end |
#admitted_scalar(value) ⇒ Object
An admitted value is a SCALAR however it arrived — as a bare string on a plain field, or wrapped in the one-field holder its type names.
88 89 90 91 92 93 |
# File 'lib/hecks/runtime/value/admission.rb', line 88 def admitted_scalar(value) return value unless value.is_a?(self) fields = value.to_h fields.size == 1 ? fields.values.first : value end |
#chapter_of(construct) ⇒ Object
UP TO THE CHAPTER, from wherever the attribute was declared. A value object's owner is its aggregate and an aggregate's owner is its chapter, so the walk stops at the first construct that can answer for an aggregate BY NAME — which is the chapter, and only the chapter.
80 81 82 83 84 |
# File 'lib/hecks/runtime/value/admission.rb', line 80 def chapter_of(construct) node = construct node = node.hecks_owner while node && !node.respond_to?(:aggregate) && node.respond_to?(:hecks_owner) node.respond_to?(:aggregate) ? node : nil end |