Class: Hecks::Bluebook::DSL::ValueObjectBuilder
- Inherits:
-
Object
- Object
- Hecks::Bluebook::DSL::ValueObjectBuilder
- Includes:
- AttributeCollector, RuleReference, WordGate
- Defined in:
- lib/hecks/bluebook/dsl/value_object_builder.rb
Constant Summary collapse
- GRAMMAR_CONTEXT =
"ValueObject"
Constants included from WordGate
Constants included from RuleReference
RuleReference::BOOTSTRAP_FALLBACK
Class Method Summary collapse
Instance Method Summary collapse
- #build ⇒ Object
-
#initialize(name, owner_value_objects: []) ⇒ ValueObjectBuilder
constructor
A new instance of ValueObjectBuilder.
-
#invariant_impl(description, &predicate) ⇒ Object
NO BLOCK is a REFERENCE, not a fresh declaration — the same move S10 already made for
CommandBuilder#given(ADR 0025, "a precondition shared across commands is declared once... a command references it by name"), one level over: a rule shared across SIBLING value objects on the same aggregate, declared once, on the first one to need it. -
#member_impl(**fields) ⇒ Object
RENAMED FROM
member— item #13's full metaprogrammed dispatch (slice 4c). -
#one_of_impl(*values, &block) ⇒ Object
THE WRAPPER BLOCK IS GONE (ADR 0025, "Attributes" — "closed sets lose the wrapper block").
Methods included from RuleReference
build_rule, lookup, resolve_hash_chain, resolve_owner_keyed, resolve_sibling_scan, verify_resolves_via!
Methods included from AttributeCollector
#attribute_impl, #attributes, #closed_sets, #list_of_impl
Constructor Details
#initialize(name, owner_value_objects: []) ⇒ ValueObjectBuilder
Returns a new instance of ValueObjectBuilder.
12 13 14 15 16 17 |
# File 'lib/hecks/bluebook/dsl/value_object_builder.rb', line 12 def initialize(name, owner_value_objects: []) @name = name @invariants = [] @members = [] @owner_value_objects = owner_value_objects end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class Hecks::Bluebook::DSL::WordGate
Class Method Details
.build(name, owner_value_objects: [], &block) ⇒ Object
145 146 147 148 149 |
# File 'lib/hecks/bluebook/dsl/value_object_builder.rb', line 145 def self.build(name, owner_value_objects: [], &block) builder = new(name, owner_value_objects: owner_value_objects) builder.instance_eval(&block) if block builder.build end |
Instance Method Details
#build ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/hecks/bluebook/dsl/value_object_builder.rb', line 130 def build if @inline_closed_set_field && attributes.size > 1 raise Malformed, "#{@name}'s one_of: on :#{@inline_closed_set_field} only works when it is the " \ "value object's only attribute — #{attributes.size} declared here; write bare " \ "member lines instead for a multi-field set" end ValueObject.declare( name: @name, attributes: attributes, invariants: @invariants, members: @members, closed_set: @closed_set || !@members.empty? ) end |
#invariant_impl(description, &predicate) ⇒ Object
NO BLOCK is a REFERENCE, not a fresh declaration — the same
move S10 already made for CommandBuilder#given (ADR 0025,
"a precondition shared across commands is declared once... a
command references it by name"), one level over: a rule
shared across SIBLING value objects on the same aggregate,
declared once, on the first one to need it. Real, live
redundancy this closes: Account's own Money/PositiveMoney
both declared invariant("a currency is a three-letter code") { currency.to_s.size == 3 }, byte for byte. Declare it before
the value objects that reference it — resolution happens at
the referencing value object's own build time, against
whatever sibling value objects the aggregate has already
built, the same ordering rule given carries.
RENAMED FROM invariant — item #13's full metaprogrammed
dispatch (slice 4b). Bootstrap-reachable, in
GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
104 105 106 107 108 109 110 111 |
# File 'lib/hecks/bluebook/dsl/value_object_builder.rb', line 104 def invariant_impl(description, &predicate) return reference_named_invariant(description) unless predicate # moved to the language: given "a rule says what it means", on Shape.Assert @invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant", extraction_failure: "it would be a rule the IR cannot carry") end |
#member_impl(**fields) ⇒ Object
RENAMED FROM member — item #13's full metaprogrammed dispatch
(slice 4c). Bootstrap-reachable, in
GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
82 83 84 85 86 |
# File 'lib/hecks/bluebook/dsl/value_object_builder.rb', line 82 def member_impl(**fields) raise Malformed, "#{@name} declared an empty member" if fields.empty? @members << fields end |
#one_of_impl(*values, &block) ⇒ Object
THE WRAPPER BLOCK IS GONE (ADR 0025, "Attributes" — "closed sets
lose the wrapper block"). member lines are bare now, written
directly in the value_object body with no one_of do ... end
around them — build's own closed_set: @closed_set || !@members.empty? already treats a non-empty @members as closed,
so nothing else has to change for that to work. A single-field set
has an even shorter spelling: attribute's own one_of: keyword
(AttributeCollector#install_inline_closed_set, overridden below).
This also removes a real, documented landmine: the block form
collided with AttributeCollector#one_of's own type-position
method (one_of("a", "b"), different arity) the moment both were
mixed into the same builder — a value_object could never actually
use the inline form on one of its own attributes. One one_of
method now, not two.
LEGACY UNDER SHADOW-PARSING (S0a's own bridge) — frozen era text
still writes the block form (2 locations in
examples/banking/data/eras/banking/1.bluebook, duplicated once
more in that era's own archive copy), so EraGuard.shadow_parse
still needs to read it. block_given? is what tells the two
calling shapes apart: the type-position form
(one_of("a", "b")) never passes a block, only the wrapper does.
RENAMED FROM one_of — item #13's full metaprogrammed dispatch
(slice 5). SAME NAME as AttributeCollector#one_of_impl's own
— required for the super(*values) call below to keep
resolving; see that method's own comment. Reached directly
through its own "ValueObject"-context Keyword row (the
block-wrapper form has its own row, distinct from "Type"'s),
not through the Type-position fallback this word's OTHER
context uses.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/hecks/bluebook/dsl/value_object_builder.rb', line 50 def one_of_impl(*values, &block) unless block # NO VALUES, NO BLOCK is the SCALAR spelling — nonsensical, not # merely inert: it names a closed set with nothing in it. The # original block form caught this by a side effect (`@closed_set # = true` ran unconditionally, before the `if block`), and this # keeps the same refusal rather than letting the call fall # through and silently do nothing. if values.empty? raise Malformed, "#{@name}'s one_of names no values — one_of(\"a\", \"b\") takes at least one, or " \ "give the attribute its own one_of: [...] for a named closed set" end # AttributeCollector's own `one_of(*values)` — Ruby's normal # `super` reaches the included module's method from here. return super(*values) end unless MetaValidator.shadow_parsing? raise Malformed, "#{@name}'s one_of do ... end wrapper is gone — give the single attribute its " \ "own one_of: [...], or write bare member lines with no wrapper for a multi-field set" end @closed_set = true instance_eval(&block) end |