Class: Hecks::Bluebook::DSL::DomainPortBuilder

Inherits:
Object
  • Object
show all
Includes:
WordGate
Defined in:
lib/hecks/bluebook/dsl/domain_port_builder.rb

Constant Summary collapse

GRAMMAR_CONTEXT =
"DomainPort"

Constants included from WordGate

WordGate::NOT_ADMITTED

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, owner: nil, legacy_bare_port: false) ⇒ DomainPortBuilder

legacy_bare_port: — ONLY Hecks.port's own top-level method (lib/hecks.rb) passes true. PortBuilder#build never refused an empty build (no verb, no signal, nothing) — Port.new(verb: nil, signal: :reply) is a real, allowed shape dsl_spec.rb's own "a port" tests rely on (signal-only, no verb at all). The AGGREGATE-scoped (BindingProxy#port) and hecksagon-ROOT (HecksagonBuilder#port_impl) callers both reach this SAME class with owner: nil too when they're building the bare-verb shape (port_impl's own root-level port can be EITHER shape, decided only after build returns) — so owner.nil? cannot be the discriminator between "old Hecks.port semantics" and "real DomainPort semantics"; those two callers correctly want the stricter "declares no verb and no operations" refusal build already raises below, unchanged. Only the literal top-level .port file caller wants the older, looser rule.



25
26
27
28
29
30
31
32
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 25

def initialize(name, owner: nil, legacy_bare_port: false)
  @name             = name
  @owner            = owner
  @operations       = []
  @signal           = :reply
  @answers          = []
  @legacy_bare_port = legacy_bare_port
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: nil, legacy_bare_port: false, &block) ⇒ Object



113
114
115
116
117
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 113

def self.build(name, owner: nil, legacy_bare_port: false, &block)
  builder = new(name, owner: owner, legacy_bare_port: legacy_bare_port)
  builder.instance_eval(&block) if block
  builder.build
end

Instance Method Details

#answers(name) ⇒ Object

THE METHOD CONTRACT — PortBuilder#answers's own twin, added here after the fact: a .port file migrated to parse through this builder (the repoint lib/hecks.rb#port's own comment describes) can still declare one (extraction.port's own answers :canonical, real, live corpus text) — this builder's bare-verb fallback needs to carry it through to the same Port object PortBuilder itself would have built, or the migration would silently drop a method-contract check for any .port file that uses this word.



101
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 101

def answers(name) = @answers << name.to_sym

#asks_impl(name, to: nil, &block) ⇒ Object

WHAT WE ASK OF THE OUTSIDE — the direction this language did not have. Before this, a domain could be CALLED by an adapter and never call one. An asks is dispatched like any other port operation, so a policy can trigger it off an event, and it comes back as one of the two events it named — which is what makes the outside world something the model can reason about rather than a place exceptions come from.

RENAMED FROM asks — item #13's full metaprogrammed dispatch (slice 4c), same reasoning as tells_impl above.



60
61
62
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 60

def asks_impl(name, to: nil, &block)
  @operations << PortOperationBuilder.build(name, to: to, owner: @owner, direction: :outbound, &block)
end

#buildObject

Raises:



103
104
105
106
107
108
109
110
111
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 103

def build
  raise Malformed, "#{@name} declares both a verb and operations — a port is one or the other, not both" if @verb && !@operations.empty?

  return MetaValidator.call_port(Port.new(name: @name, verb: @verb, signal: @signal, answers: @answers)) if @verb || (@legacy_bare_port && @operations.empty?)

  raise Malformed, "#{@name} declares no verb and no operations" if @operations.empty?

  DomainPort.new(name: @name, operations: @operations)
end

#signal(value) ⇒ Object



90
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 90

def signal(value) = @signal = value.to_sym

#tells_impl(name, to: nil, &block) ⇒ Object

WHAT THE OUTSIDE TELLS US — an external fact arriving, translated into this domain's own word for it. Spelled operation before it had a twin, and operation still works: the corpus is full of it, and renaming a word costs every chapter that uses it for no gain a reader can feel.

RENAMED FROM tells — item #13's full metaprogrammed dispatch (slice 4c). operation/tells are TWO separate Keyword rows (a word admitting two forms) that both name calls: "tells_impl" — the routing between the two spellings now lives in the table, not in a Ruby alias. Not bootstrap-reachable (checked directly), so no BOOTSTRAP_CALLS_FALLBACK entry needed.



46
47
48
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 46

def tells_impl(name, to: nil, &block)
  @operations << PortOperationBuilder.build(name, to: to, owner: @owner, direction: :inbound, &block)
end

#verb(value) ⇒ Object

Hecks.port "x" do verb "y"; signal :effect end's own two words, reachable here too — a bare-verb DomainPortBuilder.build falls back to the SAME Port object PortBuilder produces (build, below), so any .port file can migrate to being parsed by this builder with zero change to its own text, or to any caller that reads .verb/.signal off the Port it gets back. Ordinary defs, exactly like PortBuilder's own — WordGate's own header is explicit that a word answered this way never reaches its method_missing, so no new self-hosted grammar row is needed for either word under this context.



89
# File 'lib/hecks/bluebook/dsl/domain_port_builder.rb', line 89

def verb(value)   = @verb = value.to_s