Module: Hecks::Projections::Model
- Extended by:
- Hecks::Projector::Target
- Defined in:
- lib/hecks/projections/model.rb,
lib/hecks/projections/model/deviations.rb
Overview
THE MODEL CLASSES, PROJECTED FROM THE LANGUAGE THAT DECLARES THEM.
A construct's HOLDING half — its readers, its emission, a
constructor that assigns declared fields and hands off — restates
what bluebook.bluebook already says, three times over in Ruby.
This renders it instead.
ONLY THE HOLDING HALF. Behaviour::X is hand-written and permanent,
and settle is the seam: everything a declaration cannot state
lives behind it, so regenerating can never be lossy. That property
was established construct by construct before any of this was
written — the chapter looked generatable and was not, until its
@hecks_root, ports table and child stamping moved behind settle.
THE HOST MANIFEST IS THE HONEST PART. The grammar states the fields; it does not state which constructs are Ruby CLASSES rather than instances, what a constructor's defaults are, or how a value is coerced on the way in. Those are facts about Ruby, not about bluebooks, so they are declared here rather than pretended into the language — and keeping them in one table is what would let a second host swap this file rather than edit thirteen.
Defined Under Namespace
Modules: Deviations
Constant Summary collapse
- HOST =
PER-CONSTRUCT RUBY FACTS.
coerceis the only fiddly column: a declared field arrives as whatever the builder handed over, and each construct has always normalised its own on the way in. { "Policy" => { construct: "Policy", file: "policy.rb", behaviour: "Behaviour::Policy", readers: %i[name on_event trigger_command target_domain where for_each with_spec], accessors: %i[aggregate], defaults: { name: nil, on_event: "nil", trigger_command: "nil", target_domain: "nil", where: "nil", for_each: "nil", with_spec: "[]", aggregate: "nil" }, coerce: { name: ".to_s", aggregate: "&.to_s" }, # A LIST OF BINDINGS IS NOT A SCALAR ON THE WIRE. Every other # field emits as itself; this one has to render the way # `DispatchSpec`'s own `with_spec` does — keys to strings, and # `render_value` KEEPING the leading colon on a Symbol, because # a binding that reads an event field and one that supplies a # literal string are otherwise indistinguishable once written # down (see `MetaValidator::Readings`' own note on exactly that). renders: { with_spec: "-> { with_spec.map { |key, value| [key.to_s, Bluebook.render_value(value)] } }" }, settles: false } }.freeze
Class Method Summary collapse
- .call(bluebook:, options: {}) ⇒ Object
- .constructor(host) ⇒ Object
-
.emits(bluebook, name) ⇒ Object
The emission, keyed as the model spells it and sourced as the language declares it.
-
.emitted_fields(bluebook, name) ⇒ Object
WHAT THE CONSTRUCT EMITS: what the language declares, less every deviation the tables account for.
- .indent(text, by) ⇒ Object
- .readers(host) ⇒ Object
- .render(bluebook, name, host) ⇒ Object
- .wrap(text) ⇒ Object
Methods included from Hecks::Projector::Target
projection_declares, projection_emits, projection_key, projection_requires, projects_as
Class Method Details
.call(bluebook:, options: {}) ⇒ Object
60 61 62 |
# File 'lib/hecks/projections/model.rb', line 60 def call(bluebook:, options: {}) HOST.to_h { |name, host| [host.fetch(:file), render(bluebook, name, host)] } end |
.constructor(host) ⇒ Object
131 132 133 134 135 136 137 138 139 |
# File 'lib/hecks/projections/model.rb', line 131 def constructor(host) args = host.fetch(:defaults).map { |f, d| d ? "#{f}: #{d}" : "#{f}:" }.join(", ") body = host.fetch(:defaults).keys.map { |f| " @#{f} = #{f}#{host.fetch(:coerce, {})[f]}" } body << "\n settle" if host.fetch(:settles, true) "def initialize(#{args})\n#{body.join("\n")}\nend" end |
.emits(bluebook, name) ⇒ Object
The emission, keyed as the model spells it and sourced as the language declares it.
90 91 92 93 94 95 96 97 98 |
# File 'lib/hecks/projections/model.rb', line 90 def emits(bluebook, name) fields = emitted_fields(bluebook, name) renders = HOST.fetch(name).fetch(:renders, {}) width = fields.map { |f| f.to_s.length }.max.to_i "emits_ir(\n" + fields.map { |f| " #{"#{f}:".ljust(width + 1)} #{renders.fetch(f, ":#{f}")}" }.join(",\n") + "\n)" end |
.emitted_fields(bluebook, name) ⇒ Object
WHAT THE CONSTRUCT EMITS: what the language declares, less every deviation the tables account for. The generator and spec/model_shape_conformance_spec compute this the same way, from the same tables, which is the point of the tables being in lib.
104 105 106 107 108 109 110 111 112 |
# File 'lib/hecks/projections/model.rb', line 104 def emitted_fields(bluebook, name) bluebook.aggregate(name).attributes.map(&:name) .reject { |f| Deviations::PARENT_REF.call(f) } - Deviations::JUDGE_ONLY - Deviations.off_the_wire(name) - Deviations.dynamic_tail(name) - Deviations.folded(name).values.flatten - Deviations.unpacked(name).keys end |
.indent(text, by) ⇒ Object
141 |
# File 'lib/hecks/projections/model.rb', line 141 def indent(text, by) = text.lines.map { |l| l.strip.empty? ? l : (" " * by) + l }.join |
.readers(host) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/hecks/projections/model.rb', line 114 def readers(host) lines = ["attr_reader #{host.fetch(:readers).map { |r| ":#{r}" }.join(', ')}"] accessors = host.fetch(:accessors, []) return lines.join("\n") if accessors.empty? # A declared field the model deliberately does not emit still # needs a reader, and the REASON it is off the wire is carried # here rather than typed in — a comment that survives # regeneration is one the generator writes. reasons = Deviations::OFF_THE_WIRE.fetch(host.fetch(:construct, ""), {}) (lines + accessors.map { |a| why = reasons[a] (why ? "\n# #{a.upcase}, DECLARED AND DELIBERATELY OFF THE WIRE\n# #{wrap(why)}\n" : "") + "attr_accessor :#{a}" }).join("\n") end |
.render(bluebook, name, host) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/hecks/projections/model.rb', line 64 def render(bluebook, name, host) <<~RUBY # GENERATED — projected from the language's own #{name} aggregate. # DO NOT EDIT: the holding half is rendered, and #{host.fetch(:behaviour)} # is where anything hand-written belongs. require_relative "behaviour/#{File.basename(host.fetch(:file), ".rb")}" module Hecks module Bluebook class #{name} include Hecks::IR include #{host.fetch(:behaviour)} #{indent(emits(bluebook, name), 6)} #{indent(readers(host), 6)} #{indent(constructor(host), 6)} end end end RUBY end |
.wrap(text) ⇒ Object
142 |
# File 'lib/hecks/projections/model.rb', line 142 def wrap(text) = text.scan(/.{1,62}(?:\s|$)/).map(&:strip).join("\n# ") |