Module: Hecks::Forms::RecordRenderer
- Defined in:
- lib/hecks/forms/record_renderer.rb
Overview
The two pages a .bluebook doesn't declare but a browsing developer
always wants: every record of an aggregate, and one record's own
state plus what it can legally do next. Neither is a query — they
read the repository directly, the same way AggregateDoor#all/#find
do (facade/surface/aggregate_door.rb) — so they exist for every
aggregate whether or not its bluebook declared a query at all.
Class Method Summary collapse
- .applies?(aggregate, command, current_state) ⇒ Boolean
-
.command_links(domain, aggregate, id, current_state) ⇒ Object
Every non-creating command, EXCEPT a lifecycle transition that does not apply from the record's current state — the same rule
Rules#admissible_transitionenforces at dispatch, read here so a link that would only refuse is never offered in the first place. - .creating_links(domain, aggregate) ⇒ Object
- .index(registry:, domain:, aggregate:) ⇒ Object
- .query_links(domain, aggregate) ⇒ Object
-
.render_value(value) ⇒ Object
Value.materializefirst — a stored field is aRuntime::Valuewherever its attribute is a value object, not a plain Hash (see record_table.rb's own note on the same read). - .show(registry:, domain:, aggregate:, id:) ⇒ Object
- .state_table(state) ⇒ Object
- .verb_list(domain, aggregate) ⇒ Object
Class Method Details
.applies?(aggregate, command, current_state) ⇒ Boolean
85 86 87 88 89 90 |
# File 'lib/hecks/forms/record_renderer.rb', line 85 def self.applies?(aggregate, command, current_state) transitions = aggregate.lifecycle&.transitions_for(command.hecks_name) || [] return true if transitions.empty? transitions.any? { |t| t.from.nil? || Array(t.from).map(&:to_s).include?(current_state.to_s) } end |
.command_links(domain, aggregate, id, current_state) ⇒ Object
Every non-creating command, EXCEPT a lifecycle transition that does
not apply from the record's current state — the same rule
Rules#admissible_transition enforces at dispatch, read here so a
link that would only refuse is never offered in the first place.
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/hecks/forms/record_renderer.rb', line 74 def self.command_links(domain, aggregate, id, current_state) commands = aggregate.commands.reject(&:creates?).select { |cmd| applies?(aggregate, cmd, current_state) } return "<p><em>No commands act on an existing #{Escape.html(aggregate.hecks_name)}.</em></p>" if commands.empty? items = commands.map do |cmd| href = "/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html?to=#{Escape.attr(id)}" %(<li><a href="#{href}"><span>#{Escape.html(cmd.hecks_name)}</span><span class="kind">#{Escape.html(cmd.goal.to_s)}</span></a></li>) end %(<ul class="verb-list">#{items.join}</ul>) end |
.creating_links(domain, aggregate) ⇒ Object
26 27 28 29 30 31 32 33 34 |
# File 'lib/hecks/forms/record_renderer.rb', line 26 def self.creating_links(domain, aggregate) creators = aggregate.commands.select(&:creates?) return "" if creators.empty? links = creators.map { |cmd| %(<a class="button" href="/#{domain}/#{aggregate.hecks_name}/#{cmd.hecks_name}.html">+ #{Escape.html(cmd.hecks_name)}</a>) } %(<div class="actions">#{links.join}</div>) end |
.index(registry:, domain:, aggregate:) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/hecks/forms/record_renderer.rb', line 14 def self.index(registry:, domain:, aggregate:) instances = registry.repository(domain, aggregate).all <<~HTML <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}")}</h1> #{aggregate.description ? %(<p class="goal">#{Escape.html(aggregate.description)}</p>) : ''} #{creating_links(domain, aggregate)} <h2>Records (#{instances.size})</h2> #{RecordTable.render(aggregate, instances, domain: domain)} #{verb_list(domain, aggregate)} HTML end |
.query_links(domain, aggregate) ⇒ Object
92 93 94 95 96 97 98 99 |
# File 'lib/hecks/forms/record_renderer.rb', line 92 def self.query_links(domain, aggregate) return "" if aggregate.queries.empty? items = aggregate.queries.map do |query| %(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{query.hecks_name}.html"><span>#{Escape.html(query.hecks_name)}</span><span class="kind">query</span></a></li>) end %(<h2>Queries</h2><ul class="verb-list">#{items.join}</ul>) end |
.render_value(value) ⇒ Object
Value.materialize first — a stored field is a Runtime::Value
wherever its attribute is a value object, not a plain Hash (see
record_table.rb's own note on the same read).
62 63 64 65 66 67 68 |
# File 'lib/hecks/forms/record_renderer.rb', line 62 def self.render_value(value) case (value = Runtime::Value.materialize(value)) when Hash then value.map { |k, v| "#{k}: #{render_value(v)}" }.join(", ") when Array then value.map { |v| render_value(v) }.join("; ") else value.to_s end end |
.show(registry:, domain:, aggregate:, id:) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/hecks/forms/record_renderer.rb', line 36 def self.show(registry:, domain:, aggregate:, id:) instance = registry.repository(domain, aggregate).find(id) return nil unless instance state = instance.state current = aggregate.lifecycle && state[aggregate.lifecycle.field] <<~HTML <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}")} <span class="mono">#{Escape.html(id)}</span></h1> #{current ? %(<span class="badge role">#{Escape.html(aggregate.lifecycle.field)}: #{Escape.html(current)}</span>) : ''} #{state_table(state)} <h2>Commands</h2> #{command_links(domain, aggregate, id, current)} #{query_links(domain, aggregate)} HTML end |
.state_table(state) ⇒ Object
52 53 54 55 56 57 |
# File 'lib/hecks/forms/record_renderer.rb', line 52 def self.state_table(state) rows = state.map { |key, value| "<tr><th>#{Escape.html(Humanize.label(key.to_s))}</th><td>#{Escape.html(render_value(value))}</td></tr>" } %(<div class="table-scroll"><table><tbody>#{rows.join}</tbody></table></div>) end |
.verb_list(domain, aggregate) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/hecks/forms/record_renderer.rb', line 101 def self.verb_list(domain, aggregate) commands = aggregate.commands.reject(&:creates?) return "" if commands.empty? && aggregate.queries.empty? cmd_items = commands.map { |c| %(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{c.hecks_name}.html"><span>#{Escape.html(c.hecks_name)}</span><span class="kind">command</span></a></li>) } query_items = aggregate.queries.map { |q| %(<li><a href="/#{domain}/#{aggregate.hecks_name}/#{q.hecks_name}.html"><span>#{Escape.html(q.hecks_name)}</span><span class="kind">query</span></a></li>) } %(<h2>Every command & query on #{Escape.html(aggregate.hecks_name)}</h2><ul class="verb-list">#{(cmd_items + query_items).join}</ul>) end |