Class: Hecks::Bluebook::DSL::EntityBuilder
- Inherits:
-
Object
- Object
- Hecks::Bluebook::DSL::EntityBuilder
- Includes:
- AttributeCollector, IdentityDeclaration, RuleReference, WordGate
- Defined in:
- lib/hecks/bluebook/dsl/entity_builder.rb
Constant Summary collapse
- GRAMMAR_CONTEXT =
"Entity"
Constants included from WordGate
Constants included from RuleReference
RuleReference::BOOTSTRAP_FALLBACK
Class Method Summary collapse
Instance Method Summary collapse
-
#belongs_to_impl(type, as: nil, optional: false) ⇒ Object
rubocop:enable Naming/PredicatePrefix.
- #build ⇒ Object
-
#command_impl(name, from: nil, &block) ⇒ Object
from:— seeAggregateBuilder#command's own comment; the SAME guard, checked against this PIECE's own lifecycle field (S10, ADR 0025 — a piece's own state machine is checkable the same way a head's is). - #description(value) ⇒ Object
-
#entity_impl(name, &block) ⇒ Object
S17, ADR 0026 — A PIECE NESTED INSIDE A PIECE.
-
#given_impl(description, &predicate) ⇒ Object
A PRECONDITION SHARED ACROSS THIS PIECE'S OWN COMMANDS, DECLARED ONCE — the same move
AggregateBuilder#givenalready makes, one level down. -
#has_many_impl(type, as: nil, **options) ⇒ Object
has_many_impl/has_one_impl are DSL declaration keywords a domain author writes as bare has_many/has_one (matching belongs_to_impl alongside them) — not real predicates, so renaming to many?/one? per Naming/PredicatePrefix would break every bluebook that declares one.
- #has_one_impl(type, as: nil, optional: false) ⇒ Object
-
#initialize(name, owner_value_objects: [], owner_named_givens: {}, identity_name_prefix: nil, identity_value_object_installer: nil) ⇒ EntityBuilder
constructor
A new instance of EntityBuilder.
-
#invariant_impl(description, &predicate) ⇒ Object
A PIECE'S OWN SHAPE RULE (S10, ADR 0025's own "Rules" shape, one level down from
ValueObjectBuilder#invariant, whose extraction/error pattern this mirrors) — checked against EVERY INSTANCE of this piece the aggregate holds, not once against the aggregate's own flat state (Admissibility#enforce_invariants's own recursive walk). - #lifecycle_impl(field, default:, &block) ⇒ Object
- #query_impl(name, &block) ⇒ Object
-
#reference_to_impl(type, as: nil, optional: false) ⇒ Object
THE SAME FIELD AggregateBuilder's OWN reference_to BUILDS — a piece can hold a reference to another root exactly the way its own head can (Card.assignee_id, a Team's own id), just never to another PIECE, since there's no cross-piece addressing anywhere in this language to resolve one against.
Methods included from RuleReference
build_rule, lookup, resolve_hash_chain, resolve_owner_keyed, resolve_sibling_scan, verify_resolves_via!
Methods included from IdentityDeclaration
Methods included from AttributeCollector
#attribute_impl, #attributes, #closed_sets, #list_of_impl, #one_of_impl
Constructor Details
#initialize(name, owner_value_objects: [], owner_named_givens: {}, identity_name_prefix: nil, identity_value_object_installer: nil) ⇒ EntityBuilder
Returns a new instance of EntityBuilder.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 13 def initialize(name, owner_value_objects: [], owner_named_givens: {}, identity_name_prefix: nil, identity_value_object_installer: nil) @name = name @commands = [] @queries = [] @entities = [] @named_givens = {} @invariants = [] @owner_value_objects = owner_value_objects @identity_name_prefix = identity_name_prefix || Naming.demodulise(name) @identity_value_object_installer = identity_value_object_installer # THE AGGREGATE-WIDE cross-entity given pool — ONE hash, the # SAME object, threaded unchanged through every piece nested # under one aggregate however deep (the identical shape # `@owner_value_objects` already threads — see this class' # own `entity` comment). `given`'s own block form writes # through to it; a SIBLING piece's bare command-level # reference reads from it via `CommandBuilder# # reference_named_given`. @owner_named_givens = owner_named_givens # DEFERRED CONSTRUCTION — see `AggregateBuilder#drain_pending!`'s # own comment; the identical mechanism, one level down, so a # nested piece's own commands (Dispatch inside Handler) see # every SIBLING entity/command/query this piece goes on to # declare, not just whatever came before it textually. @pending_entities = [] @pending_commands = [] @pending_queries = [] 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: [], owner_named_givens: {}, identity_name_prefix: nil, identity_value_object_installer: nil, &block) ⇒ Object
205 206 207 208 209 210 211 212 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 205 def self.build(name, owner_value_objects: [], owner_named_givens: {}, identity_name_prefix: nil, identity_value_object_installer: nil, &block) builder = new(name, owner_value_objects: owner_value_objects, owner_named_givens: owner_named_givens, identity_name_prefix: identity_name_prefix, identity_value_object_installer: identity_value_object_installer) builder.instance_eval(&block) if block builder.build end |
Instance Method Details
#belongs_to_impl(type, as: nil, optional: false) ⇒ Object
rubocop:enable Naming/PredicatePrefix
84 85 86 87 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 84 def belongs_to_impl(type, as: nil, optional: false) target = Naming.demodulise(type) relationship_attribute(target, :belongs_to, as || Naming.snake(target).to_sym, optional: optional) end |
#build ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 186 def build drain_pending! resolve_pending_identity! seal_lifecycle_guards install_closed_sets! Entity.declare( name: @name, description: @description, identified_by: @identity_paths, attributes: attributes, commands: @commands, queries: @queries, entities: @entities, preconditions: @named_givens.values, invariants: @invariants, lifecycle: @lifecycle ) end |
#command_impl(name, from: nil, &block) ⇒ Object
from: — see AggregateBuilder#command's own comment; the
SAME guard, checked against this PIECE's own lifecycle field
(S10, ADR 0025 — a piece's own state machine is checkable the
same way a head's is).
RENAMED FROM command/query/entity/lifecycle (all below)
— item #13's full metaprogrammed dispatch (slice 4c), same
reasoning as AggregateBuilder's own siblings: bootstrap-
reachable, in BOOTSTRAP_CALLS_FALLBACK.
106 107 108 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 106 def command_impl(name, from: nil, &block) @pending_commands << [name, from, block] end |
#description(value) ⇒ Object
43 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 43 def description(value) = @description = value |
#entity_impl(name, &block) ⇒ Object
S17, ADR 0026 — A PIECE NESTED INSIDE A PIECE. "A Dispatch
[has] no life outside its Handler" (the ADR's own words) —
the same reason Member nests inside ValueObject, one level
further in. owner_value_objects passes straight through
unchanged, not re-derived from this entity's own attributes —
a piece mints no value objects of its own at any depth, so a
NESTED piece's bare identified_by :field still resolves
against the SAME root aggregate's value objects an outer
piece's already does (AggregateBuilder#entity's own comment
names this pool ; there is exactly one of them, however deep
the nesting goes).
125 126 127 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 125 def entity_impl(name, &block) @pending_entities << [name, block] end |
#given_impl(description, &predicate) ⇒ Object
A PRECONDITION SHARED ACROSS THIS PIECE'S OWN COMMANDS, DECLARED
ONCE — the same move AggregateBuilder#given already makes,
one level down. Real, live redundancy this closes: banking's
own LedgerEntry.Amend/LedgerEntry.Reverse each repeated
given("customer is active") { parent.customer.status == "active" } and given("account is open") { parent.status == "open" }, byte for byte, because a piece had no way to declare
either once and reference it back — only the AGGREGATE could.
DECLARE BEFORE THE COMMANDS THAT REFERENCE IT, the same
ordering AggregateBuilder#given's own comment names — though
since ADR 0028, command only queues a descriptor and actually
builds at #drain_pending! time, well after this whole block
(including every given in it) has already run, so textual
order within the block no longer actually matters here; named
for the reader anyway, since given's own resolution logic
(CommandBuilder#reference_named_given) still reads whatever
@named_givens holds AT THE COMMAND'S OWN BUILD TIME, not by
magic.
RENAMED FROM given — item #13's full metaprogrammed dispatch
(slice 4b), same reasoning as reference_to_impl above.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 153 def given_impl(description, &predicate) named = build_rule(Given, description, predicate, owner_name: @name, word: "given", extraction_failure: "its source could not be read, so no other runtime could ever evaluate it") @named_givens[description] = named # WRITE-THROUGH, first-declared-wins (`||=`) — a SECOND piece # under the same aggregate independently declaring the exact # same description stays purely local to itself (no silent # overwrite of whatever the first piece already shared; # real, live case a fuzzer or a future codemod could easily # surface: two pieces phrasing an UNRELATED rule identically # by coincidence, same as an aggregate-level given already # tolerates today). @owner_named_givens[description] ||= named end |
#has_many_impl(type, as: nil, **options) ⇒ Object
has_many_impl/has_one_impl are DSL declaration keywords a domain
author writes as bare has_many/has_one (matching belongs_to_impl
alongside them) — not real predicates, so renaming to many?/one?
per Naming/PredicatePrefix would break every bluebook that
declares one. New on Entity (Wave 6, identity-and-relationships
arc) — pieces never had relationship words before; named
*_impl to match AggregateBuilder's own siblings, item #13's
full metaprogrammed dispatch convention.
rubocop:disable Naming/PredicatePrefix
68 69 70 71 72 73 74 75 76 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 68 def has_many_impl(type, as: nil, **) unless .empty? raise Malformed, "#{@name}.has_many takes no #{.keys.first}: — an empty list already means none" end plural = Naming.demodulise(type) relationship_attribute(Naming.singularize(plural), :has_many, as || Naming.snake(plural).to_sym, list: true) end |
#has_one_impl(type, as: nil, optional: false) ⇒ Object
78 79 80 81 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 78 def has_one_impl(type, as: nil, optional: false) target = Naming.demodulise(type) relationship_attribute(target, :has_one, as || Naming.snake(target).to_sym, optional: optional) end |
#invariant_impl(description, &predicate) ⇒ Object
A PIECE'S OWN SHAPE RULE (S10, ADR 0025's own "Rules" shape,
one level down from ValueObjectBuilder#invariant, whose
extraction/error pattern this mirrors) — checked against
EVERY INSTANCE of this piece the aggregate holds, not once
against the aggregate's own flat state
(Admissibility#enforce_invariants's own recursive walk).
No reference-by-name form (unlike given) — no known corpus
need for a piece's own invariant to be shared with a SIBLING
piece yet; if that need shows up, it is given's own
cross-entity write-through pattern to extend, not a reason to
invent a second one here speculatively.
RENAMED FROM invariant — item #13's full metaprogrammed
dispatch (slice 4b), same reasoning as given_impl above.
181 182 183 184 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 181 def invariant_impl(description, &predicate) @invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant", extraction_failure: "it would be a rule the IR cannot carry") end |
#lifecycle_impl(field, default:, &block) ⇒ Object
129 130 131 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 129 def lifecycle_impl(field, default:, &block) @lifecycle = LifecycleBuilder.build(field, default: default, &block) end |
#query_impl(name, &block) ⇒ Object
110 111 112 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 110 def query_impl(name, &block) @pending_queries << [name, block] end |
#reference_to_impl(type, as: nil, optional: false) ⇒ Object
THE SAME FIELD AggregateBuilder's OWN reference_to BUILDS — a
piece can hold a reference to another root exactly the way its
own head can (Card.assignee_id, a Team's own id), just never
to another PIECE, since there's no cross-piece addressing
anywhere in this language to resolve one against.
RENAMED FROM reference_to — item #13's full metaprogrammed
dispatch (slice 4b). Bootstrap-reachable, in
GenericDispatch::BOOTSTRAP_CALLS_FALLBACK.
53 54 55 56 57 |
# File 'lib/hecks/bluebook/dsl/entity_builder.rb', line 53 def reference_to_impl(type, as: nil, optional: false) target = Naming.demodulise(type) relationship_attribute(target, :reference_to, as || default_reference_name(target), optional: optional) end |