Class: Phlex::Reactive::ParamSchema
- Inherits:
-
Object
- Object
- Phlex::Reactive::ParamSchema
- Defined in:
- lib/phlex/reactive/param_schema.rb
Overview
A COMPILED param schema (issue #109). The coerce family used to live inline in ActionsController; here it's a standalone unit built ONCE at declaration (Component.action -> ParamSchema.compile) so:
* an unknown type symbol raises Phlex::Reactive::UnknownParamType at class
load instead of silently `to_s`-ing at request time, and
* the drop-don't-fabricate coercion is unit-testable without a booted
request spec.
The behavior is byte-for-byte what the controller did: the built-ins keep their exact semantics ("abc".to_i => 0, not DROP; :file duck-type DROP; the array/hash DROP rules), the bracket-expansion/deep-merge matrix (#16/#21/ #24/#39) is UNCHANGED, and the verbose_errors dropped-param collector (#82/ #87) threads through unchanged — a nil collector is the zero-cost prod path.
A type is one of:
* a scalar symbol (:string/:integer/:float/:boolean/:file/:date/
:datetime/:decimal, or an app-registered type)
* a Hash schema ({ id: :integer, ... }) — nested object
* a one-element Array ([:integer] / [{ ... }]) — array of that
Constant Summary collapse
- DROP =
Sentinel: a declared key whose value can't be coerced to its type is DROPPED (not assigned), so the method's keyword default applies — exactly as if the client had omitted the key. Distinct from a coerced nil/[]. A custom param_type callable returns this to reject a value while keeping the drop-don't-fabricate contract, so it is PUBLIC.
Object.new
Instance Attribute Summary collapse
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Class Method Summary collapse
-
.built_in_types ⇒ Object
The frozen-semantics built-in registry, rebuilt on demand (the module dups it so app registration doesn't mutate this template).
-
.compile(schema) ⇒ Object
Validate
schemarecursively against the param-type registry and return a compiled ParamSchema.
Instance Method Summary collapse
-
#coerce(params, dropped = nil) ⇒ Object
Coerce client params against this schema.
-
#initialize(schema) ⇒ ParamSchema
constructor
A new instance of ParamSchema.
Constructor Details
#initialize(schema) ⇒ ParamSchema
Returns a new instance of ParamSchema.
145 146 147 |
# File 'lib/phlex/reactive/param_schema.rb', line 145 def initialize(schema) @schema = schema end |
Instance Attribute Details
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
143 144 145 |
# File 'lib/phlex/reactive/param_schema.rb', line 143 def schema @schema end |
Class Method Details
.built_in_types ⇒ Object
The frozen-semantics built-in registry, rebuilt on demand (the module
dups it so app registration doesn't mutate this template). Each entry
is a callable(value) -> coerced | DROP. The scalar casts here are the
EXACT ones the controller ran, so the existing request-spec matrix is
unchanged.
rubocop:disable Style/ItBlockParameter, Style/SymbolProc
Explicit (value) params, NOT it or &:to_s: the date/datetime/decimal
casters wrap an INNER parse_or_drop { ... } block, and it there would
bind to that inner block's (absent) param, not the caster's value — the
#109 autocorrect trap. Keep the WHOLE family explicit so it reads
uniformly and no caster can be collapsed to a zero-arity lambda.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/phlex/reactive/param_schema.rb', line 63 def built_in_types { string: ->(value) { value.to_s }, integer: ->(value) { value.to_i }, float: ->(value) { value.to_f }, boolean: ->(value) { BOOLEAN_TYPE.cast(value) }, # :file and the composite types (Hash/Array) are handled structurally # in #coerce, not via a scalar callable — the registry entry exists so # compile-time validation recognizes the symbol. A nil callable means # "handled structurally"; #coerce never dispatches it here. file: nil, date: ->(value) { parse_or_drop { Date.iso8601(value.to_s) } }, datetime: ->(value) { parse_or_drop { DateTime.iso8601(value.to_s) } }, decimal: ->(value) { parse_or_drop { BigDecimal(value.to_s) } } } end |
.compile(schema) ⇒ Object
Validate schema recursively against the param-type registry and
return a compiled ParamSchema. Raises UnknownParamType at the FIRST
unknown type symbol (naming the full bracketed path), so a typo fails
loudly at declaration. A blank schema compiles to an empty schema (the
action drops everything the client posts).
46 47 48 49 50 |
# File 'lib/phlex/reactive/param_schema.rb', line 46 def compile(schema) schema = {} if schema.nil? validate!(schema, nil) new(schema) end |
Instance Method Details
#coerce(params, dropped = nil) ⇒ Object
Coerce client params against this schema. Anything not in the schema is dropped — no raw mass assignment reaches the component. The top-level params (an ActionController::Parameters or a plain Hash) coerce against the hash schema (same recursion as nested hashes).
dropped is the verbose_errors diagnostics collector: an Array that
accumulates [bracketed_path, :undeclared|:uncoercible] entries. Pass nil
(the production path) and every diagnostic branch early-returns — zero
extra work.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/phlex/reactive/param_schema.rb', line 158 def coerce(params, dropped = nil) # A schema-less action drops EVERYTHING the client posted. Under a # verbose collector, record every posted key as :undeclared (forgetting # `params:` entirely is the loudest silent drop — #87); otherwise it's a # cost-free {} return on the production path. if @schema.empty? collect_undeclared(dropped, to_param_hash(params), {}, nil) if dropped return {} end # The TOP-LEVEL params container is the request's `params[:params]` — a # coerced-away malformed top level (a stray non-hash) is normalized back # to {} here, never DROP: the whole point of the top level is to hold the # coerced kwargs, so a bad container yields "no params", not a dropped # action argument. Nested malformed hashes DO drop (see coerce_hash). coerced = coerce_hash(params, @schema, dropped, nil) coerced.equal?(DROP) ? {} : coerced end |