Module: Hecks::Forms::Params
- Defined in:
- lib/hecks/forms/params.rb
Overview
The two directions between a flat, dotted, ALL-STRINGS web payload
({"amount.cents"=>"1050", "amount.currency"=>"USD"}, whether it came
off a POST form body or a GET query string — Rack hands back the same
flat shape for both as long as nothing uses [] bracket names) and the
nested, typed hash Dispatcher#dispatch/#query actually take
({amount: {cents: 1050, currency: "USD"}}).
extract NEEDS the Field tree, not just the raw params — a numeric
leaf's own runtime check (Value::Coercion#check_numeric_fields)
requires an actual Integer/Float, not a String that merely looks
like one (given.is_a?(expected), no coercion attempted there); a web
form can only ever hand back strings, so the cast has to happen here,
once, using the SAME shape FieldShape already resolved for
rendering the input in the first place — one reading of the IR, not
two that could disagree.
Class Method Summary collapse
- .cast_scalar(field, text) ⇒ Object
- .checkbox(raw_value) ⇒ Object
- .collect(field, raw, pairs) ⇒ Object
-
.extract(fields, raw) ⇒ Object
Every LEAF field carries its own full dotted path regardless of how deep
FieldShapenested it to get there — a single-attribute value object unwraps to a leaf sitting at the TOP of the fields array with a two-segment path ("reference.value"), the exact same shape a:group's own child carries. - .extract_leaf(field, raw) ⇒ Object
-
.extract_list(field, raw) ⇒ Object
One line of the textarea per element.
-
.flatten(fields, values, into: {}) ⇒ Object
The other direction — a nested value hash back to the flat dotted pairs a GET link's query string carries, so a query view's "shareable link" and its filter FORM stay two renderings of the SAME data rather than two formats that can drift.
- .nest(pairs) ⇒ Object
-
.paths(fields, into: []) ⇒ Object
Every leaf/list path a field tree carries, independent of any values — what
command_form_renderer.rb's inspect panel wants ("which fields does this command take"), whereflattenabove wants "what does THIS submission look like" and returns nothing for a field nothing was entered for. - .to_query_string(fields, values) ⇒ Object
Class Method Details
.cast_scalar(field, text) ⇒ Object
84 85 86 87 88 89 |
# File 'lib/hecks/forms/params.rb', line 84 def self.cast_scalar(field, text) case field.kind when :number then field.step == "1" ? Integer(text) : Float(text) else text end end |
.checkbox(raw_value) ⇒ Object
80 81 82 |
# File 'lib/hecks/forms/params.rb', line 80 def self.checkbox(raw_value) %w[on 1 true].include?(raw_value.to_s.downcase) end |
.collect(field, raw, pairs) ⇒ Object
36 37 38 39 40 41 42 |
# File 'lib/hecks/forms/params.rb', line 36 def self.collect(field, raw, pairs) case field.kind when :group, :money then field.children.each { |child| collect(child, raw, pairs) } when :list then (value = extract_list(field, raw)) == SKIP || pairs[field.path] = value else (value = extract_leaf(field, raw)) == SKIP || pairs[field.path] = value end end |
.extract(fields, raw) ⇒ Object
Every LEAF field carries its own full dotted path regardless of how
deep FieldShape nested it to get there — a single-attribute value
object unwraps to a leaf sitting at the TOP of the fields array with
a two-segment path ("reference.value"), the exact same shape a
:group's own child carries. So this collects every leaf as
(full path -> value) FIRST, and nests by the path's OWN segments
last — one nesting rule, blind to how a field arrived at its path.
30 31 32 33 34 |
# File 'lib/hecks/forms/params.rb', line 30 def self.extract(fields, raw) pairs = {} fields.each { |field| collect(field, raw, pairs) } nest(pairs) end |
.extract_leaf(field, raw) ⇒ Object
71 72 73 74 75 76 77 78 |
# File 'lib/hecks/forms/params.rb', line 71 def self.extract_leaf(field, raw) return checkbox(raw[field.path]) if field.kind == :boolean text = raw[field.path] return SKIP if (text.nil? || text.empty?) && field.optional? cast_scalar(field, text) end |
.extract_list(field, raw) ⇒ Object
One line of the textarea per element. A line that itself needs several fields (a multi-attribute value object as a list element) is read as JSON on that one line — the honest fallback documented in docs/command-form-and-query-form-bluebook.md rather than a second widget this prototype doesn't build yet.
61 62 63 64 65 66 67 68 69 |
# File 'lib/hecks/forms/params.rb', line 61 def self.extract_list(field, raw) text = raw[field.path] return SKIP if text.nil? || text.strip.empty? item = field.children.first text.each_line.map(&:strip).reject(&:empty?).map do |line| item.leaf? ? cast_scalar(item, line) : JSON.parse(line, symbolize_names: true) end end |
.flatten(fields, values, into: {}) ⇒ Object
The other direction — a nested value hash back to the flat dotted
pairs a GET link's query string carries, so a query view's
"shareable link" and its filter FORM stay two renderings of the
SAME data rather than two formats that can drift. Reads with
FieldRenderer.dig — the identical full-path lookup a re-rendered
input's own value comes from — for the same reason extract above
nests by full path rather than by tree shape.
98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/hecks/forms/params.rb', line 98 def self.flatten(fields, values, into: {}) fields.each do |field| case field.kind when :group, :money then flatten(field.children, values, into: into) when :list then into[field.path] = Array(FieldRenderer.dig(values, field.path)).join("\n") else value = FieldRenderer.dig(values, field.path) into[field.path] = value.to_s unless value.nil? end end into end |
.nest(pairs) ⇒ Object
44 45 46 47 48 49 50 51 |
# File 'lib/hecks/forms/params.rb', line 44 def self.nest(pairs) pairs.each_with_object({}) do |(path, value), result| segments = path.to_s.split(".").map(&:to_sym) leaf = segments.pop node = segments.reduce(result) { |acc, segment| acc[segment] ||= {} } node[leaf] = value end end |
.paths(fields, into: []) ⇒ Object
Every leaf/list path a field tree carries, independent of any
values — what command_form_renderer.rb's inspect panel wants
("which fields does this command take"), where flatten above wants
"what does THIS submission look like" and returns nothing for a
field nothing was entered for.
116 117 118 119 120 121 122 123 124 |
# File 'lib/hecks/forms/params.rb', line 116 def self.paths(fields, into: []) fields.each do |field| case field.kind when :group, :money then paths(field.children, into: into) else into << field.path end end into end |
.to_query_string(fields, values) ⇒ Object
126 127 128 129 |
# File 'lib/hecks/forms/params.rb', line 126 def self.to_query_string(fields, values) pairs = flatten(fields, values) pairs.map { |key, value| "#{URI.encode_www_form_component(key)}=#{URI.encode_www_form_component(value)}" }.join("&") end |