Module: Hecks::Naming

Defined in:
lib/hecks/naming.rb

Constant Summary collapse

IDENTITY_JOIN =

WHAT SEPARATES THE PARTS OF A DERIVED IDENTITY.

An identity of several parts is their JOIN, and the join has to be spelled the same everywhere or two readers name two different records off one declaration. It was spelled three ways at once here — "::" for an aggregate under a chapter, "." for everything under an aggregate, "#" for the three that keyed off position — and each was written where it happened to be needed. Once the runtime derived the same identity, the runtime made a FOURTH, and a reference that resolved by string comparison found nothing.

":".freeze

Class Method Summary collapse

Class Method Details

.command_ref(value) ⇒ Object

trigger Account::Debit / dispatch Account::Debit — a bare CONSTANT reference (ConstShim's own ScopedConstant, S0b), not text (ADR 0025, "events and reactions" — command references become first-class). Ruby's :: joins EVERY segment the same way a constant path always does, but a command's own hecks_fqn joins its aggregate with . (Construct#hecks_separator's default, only an AGGREGATE overrides it to ::) — so only the LAST :: becomes a .; everything before it (the chapter, when a domain is spelled at all: Banking::Account::Debit) stays ::-joined.

A STRING PASSES THROUGH UNCHANGED, on purpose — legacy era text (S0a's own shadow-parsed spelling) already mixes :: (domain) and . (command) correctly on its own, e.g. "Banking::Account.Debit", and re-splitting that by content rather than by TYPE would corrupt it (its own last :: sits between the domain and the aggregate, not the aggregate and the command). Only an actual constant object — never seen holding a . of its own — needs the rewrite at all.



113
114
115
116
117
118
119
# File 'lib/hecks/naming.rb', line 113

def command_ref(value)
  return value.to_s if value.is_a?(::String) || value.is_a?(::Symbol)

  text = value.to_s
  path, _, command = text.rpartition("::")
  path.empty? ? text : "#{path}.#{command}"
end

.demodulise(type) ⇒ Object



19
20
21
# File 'lib/hecks/naming.rb', line 19

def demodulise(type)
  type.to_s.split("::").last.to_s
end

.identity(parts) ⇒ Object

The parts of an identity, joined in declaration order.



17
# File 'lib/hecks/naming.rb', line 17

def identity(parts) = Array(parts).join(IDENTITY_JOIN)

.pascal(text) ⇒ Object

snake_case -> PascalCase. The name a synthesised closed-set value object takes when an attribute declares one inline. The derivation is part of the IR contract: the same bluebook must always produce the same name.



26
27
28
# File 'lib/hecks/naming.rb', line 26

def pascal(text)
  text.to_s.split("_").map { |part| part.sub(/\A(.)/) { Regexp.last_match(1).upcase } }.join
end

.plural(text) ⇒ Object

The name a COLLECTION of something takes.

There were two of these and one was wrong. A read model's gathered heads derived their name with a bare "#{snake(target)}s", so the meta-domain's own whole-bluebook read model handed back querys, entitys, policys and dispatchs — and every check was green, because the checks compared the wrong rule against itself. Agreement is not correctness; it never was.

So: one pluraliser, three rules, and every collection name flows through it.



46
47
48
49
50
51
52
# File 'lib/hecks/naming.rb', line 46

def plural(text)
  word = text.to_s
  return "#{word[0..-2]}ies" if word.match?(/[^aeiou]y\z/)
  return "#{word}es"         if word.match?(/(s|x|z|ch|sh)\z/)

  "#{word}s"
end

.qualifier(dotted) ⇒ Object



78
79
80
81
# File 'lib/hecks/naming.rb', line 78

def qualifier(dotted)
  text = dotted.to_s
  text.include?(".") ? text.split(".", 2).first : nil
end

.reference_key(type) ⇒ Object



69
70
71
# File 'lib/hecks/naming.rb', line 69

def reference_key(type)
  snake(demodulise(type)).to_sym
end

.singularize(text) ⇒ Object

has_many's undo — the plural WRITTEN, back to the singular the target aggregate is actually named. Deliberately the crude half of a pair: plural above earns its precision (three suffix rules) because getting a COLLECTION name wrong reads as a typo forever ; this only ever recovers a name someone already wrote as a real aggregate, so "ies -> y, trailing s dropped" is the whole rule — enough for has_many Invoices to resolve to the aggregate actually named Invoice.



61
62
63
64
65
66
67
# File 'lib/hecks/naming.rb', line 61

def singularize(text)
  word = text.to_s
  return "#{word[0..-4]}y" if word.length > 3 && word.end_with?("ies")
  return word[0..-2]       if word.length > 1 && word.end_with?("s")

  word
end

.snake(text) ⇒ Object



30
31
32
33
34
35
# File 'lib/hecks/naming.rb', line 30

def snake(text)
  text.to_s
      .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
      .gsub(/([a-z\d])([A-Z])/, '\1_\2')
      .downcase
end

.split_dotted(dotted) ⇒ Object



73
74
75
76
# File 'lib/hecks/naming.rb', line 73

def split_dotted(dotted)
  first, second = dotted.to_s.split(".", 2)
  [first.to_s, second.to_s]
end

.split_verb(verb) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/hecks/naming.rb', line 88

def split_verb(verb)
  path, command = verb.to_s.split(".", 2)
  domain, aggregate = path.to_s.split("::", 2)
  return nil unless domain && aggregate && command

  [domain, aggregate, command]
end

.unqualified(dotted) ⇒ Object



83
84
85
86
# File 'lib/hecks/naming.rb', line 83

def unqualified(dotted)
  text = dotted.to_s
  text.include?(".") ? text.split(".", 2).last : text
end