Module: Hecks::Projections::Statements
- Extended by:
- Hecks::Projector::Target
- Defined in:
- lib/hecks/projections/statements.rb
Overview
A DOMAIN'S OWN DECLARED FACTS, PROJECTED AS PLAIN ENGLISH SENTENCES
— one atomic, independently-checkable statement per fact, not a
flowing document (Projections::Reference/DocsProjector already
do that job well; this is a flat list a caller can iterate, diff,
or hand to something else entirely — a review, a test-plan seed,
a sanity check that the domain still says what someone thinks it
says).
NEVER INVENTS A SENTENCE FROM NOTHING — DocsProjector's own
discipline, held here too: a relationship's sentence is built
MECHANICALLY from its own declared shape (holder, target,
relationship kind — the same facts Projections::Diagrams's own
relationship_edge already reads), and an invariant's sentence IS
the domain author's own description, capitalized and
punctuated, never paraphrased. If a fact has no author-written
description and no unambiguous mechanical phrasing, it doesn't
get a sentence here — a wrong sentence is worse than a missing
one.
REACHABLE THE SAME WAY EVERY PROJECTION ALREADY IS, no new facade
wiring needed: Pizzas.project(Projections::Statements) —
Facade::Surface::Chapter#project's own comment already settled
this ("anything genuinely needing the graph is a projector, and a
projector is given it"). bin/statements is a thin, optional
convenience for reaching the same call from a shell.
MVP SCOPE: "has many"/"has a"/"belongs to"/"references" sentences for every list or relationship attribute, and every invariant's own description (aggregate-level and every nested value object's), verbatim. Lifecycle transitions and command given/ensures read the same way and are the natural next sentences — not built yet.
Class Method Summary collapse
-
.article(word) ⇒ Object
"AN ACCOUNT", NOT "A ACCOUNT" — every subject/object noun here is a bare construct name (
ATMCard,ExternalTransfer,Account), never free text, so the ordinary "starts with a vowel LETTER" heuristic is safe: this language's own naming never produces the English exceptions that heuristic gets wrong (an "hour", a "university") because a construct name is always spelled as a plain word, never an abbreviation read letter-by-letter or a word starting with a consonant LETTER but a vowel SOUND. - .attribute_statement(holder, attribute) ⇒ Object
-
.attribute_statements(holder) ⇒ Object
"MANY" READS THE SAME WAY REGARDLESS OF WHAT'S BEHIND IT — a
has_manyrelationship to another aggregate and a plainlist_of(Topping)value-object attribute are the same idea to someone reading the domain in English ("an Order has many toppings" is true either way), even though they're two different IR shapes (aReferenceversus an ordinary type). - .call(bluebook:, options: {}) ⇒ Object
- .holders(bluebook) ⇒ Object
-
.invariant_statement(invariant) ⇒ Object
THE DOMAIN AUTHOR'S OWN WORDS, CAPITALIZED AND PUNCTUATED — nothing else.
-
.invariant_statements(holder) ⇒ Object
INVARIANTS LIVE IN TWO PLACES — directly on the holder (an aggregate-level rule, checked after every command) and on every value object nested inside it —
DocsProjector#rules_of's ownvalue_object_forlookup is the precedent for walking both. - .statements_for(holder) ⇒ Object
Methods included from Hecks::Projector::Target
projection_declares, projection_emits, projection_key, projection_requires, projects_as
Class Method Details
.article(word) ⇒ Object
"AN ACCOUNT", NOT "A ACCOUNT" — every subject/object noun here is
a bare construct name (ATMCard, ExternalTransfer, Account),
never free text, so the ordinary "starts with a vowel LETTER"
heuristic is safe: this language's own naming never produces the
English exceptions that heuristic gets wrong (an "hour", a
"university") because a construct name is always spelled as a
plain word, never an abbreviation read letter-by-letter or a
word starting with a consonant LETTER but a vowel SOUND.
88 |
# File 'lib/hecks/projections/statements.rb', line 88 def article(word) = word.to_s.match?(/\A[AEIOUaeiou]/) ? "An" : "A" |
.attribute_statement(holder, attribute) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/hecks/projections/statements.rb', line 67 def attribute_statement(holder, attribute) subject = "#{article(holder.hecks_name)} #{holder.hecks_name}" return "#{subject} has many #{attribute.name}." if attribute.list? target = attribute.relationship && attribute.type.target_name target_phrase = target && "#{article(target).downcase} #{target}" case attribute.relationship when "has_one" then "#{subject} has #{target_phrase}." when "belongs_to" then "#{subject} belongs to #{target_phrase}." when "reference_to" then "#{subject} references #{target_phrase}." end end |
.attribute_statements(holder) ⇒ Object
"MANY" READS THE SAME WAY REGARDLESS OF WHAT'S BEHIND IT — a
has_many relationship to another aggregate and a plain
list_of(Topping) value-object attribute are the same idea to
someone reading the domain in English ("an Order has many
toppings" is true either way), even though they're two
different IR shapes (a Reference versus an ordinary type).
attribute.name carries the noun, not the target's own class
name, because the field's own name is what the domain author
actually chose to call the collection — "toppings", not
"Topping".
63 64 65 |
# File 'lib/hecks/projections/statements.rb', line 63 def attribute_statements(holder) holder.attributes.filter_map { |attribute| attribute_statement(holder, attribute) } end |
.call(bluebook:, options: {}) ⇒ Object
43 44 45 |
# File 'lib/hecks/projections/statements.rb', line 43 def call(bluebook:, options: {}) holders(bluebook).flat_map { |holder| statements_for(holder) } end |
.holders(bluebook) ⇒ Object
47 |
# File 'lib/hecks/projections/statements.rb', line 47 def holders(bluebook) = bluebook.aggregates.flat_map { |aggregate| [aggregate, *aggregate.entities] } |
.invariant_statement(invariant) ⇒ Object
THE DOMAIN AUTHOR'S OWN WORDS, CAPITALIZED AND PUNCTUATED —
nothing else. invariant("a pizza is named") already reads as
a sentence; this is the entire transformation.
103 104 105 106 107 |
# File 'lib/hecks/projections/statements.rb', line 103 def invariant_statement(invariant) text = invariant.description.to_s.strip text = "#{text[0].upcase}#{text[1..]}" if text[0] text.end_with?(".", "!", "?") ? text : "#{text}." end |
.invariant_statements(holder) ⇒ Object
INVARIANTS LIVE IN TWO PLACES — directly on the holder (an
aggregate-level rule, checked after every command) and on every
value object nested inside it — DocsProjector#rules_of's own
value_object_for lookup is the precedent for walking both.
94 95 96 97 98 |
# File 'lib/hecks/projections/statements.rb', line 94 def invariant_statements(holder) own = Array(holder.respond_to?(:invariants) ? holder.invariants : []) nested = Array(holder.respond_to?(:value_objects) ? holder.value_objects : []).flat_map(&:invariants) (own + nested).map { |invariant| invariant_statement(invariant) } end |
.statements_for(holder) ⇒ Object
49 50 51 |
# File 'lib/hecks/projections/statements.rb', line 49 def statements_for(holder) attribute_statements(holder) + invariant_statements(holder) end |