Class: Axn::Validation::Base
- Inherits:
-
Object
- Object
- Axn::Validation::Base
- Includes:
- ActiveModel::Validations
- Defined in:
- lib/axn/core/validation/base.rb
Overview
Shared kernel for the one-off ActiveModel validator classes Fields and Subfields build: the
custom validator constants and symbol-argument delegation to the action. Subclasses supply the
value source (read_attribute_for_validation) and how to reach the action
(_action_for_validation).
Direct Known Subclasses
Constant Summary collapse
- ModelValidator =
NOTE: exposing the validators as constants here (rather than registering them globally) scopes them to axn's own one-off validator classes, so they can't affect the consuming apps' validators.
Validators::ModelValidator
- TypeValidator =
Validators::TypeValidator
- ValidateValidator =
Validators::ValidateValidator
- OfValidator =
Validators::OfValidator
- ShapeValidator =
Validators::ShapeValidator
- NonEmptinessValidator =
Validators::NonEmptinessValidator
Class Method Summary collapse
-
.acceptance_admits_nil?(entry_opts) ⇒ Boolean
Whether an
acceptance:ENTRY would let a nil through. -
.declared_length_checks(entry_opts) ⇒ Object
The size checks a
length:entry actually runs, resolved the way ActiveModel's LengthValidator resolves its own options (activemodel 7.2.2.2):in:/within:expand intominimum:/maximum:(a beginless/endless range contributing only the bound it has, an exclusive end counting one less), and an entry that is explicitly blank-INtolerant with neitherminimum:noris:of its own picks up AM's implicitminimum: 1. -
.declared_length_floor(entry_opts) ⇒ Object
The smallest size a
length:entry admits, read from the checks it runs. -
.effective_entry_options(entry, declaration_options) ⇒ Object
ONE entry's options as
validateswill hand them to the validator: the declaration-wide shared options with the entry's own merged over them, which is literally what AM builds (defaults.merge(_parse_validates_options(options)), activemodel 7.2.2.2). -
.emittable_length_floor?(floor) ⇒ Boolean
Whether a floor read by
declared_length_flooris one a JSON SchemaminItems/minProperties/minLengthcan carry: a positive Integer, the only shape a size constraint takes. -
.entry_context_scoped?(entry_opts) ⇒ Boolean
Whether a validator ENTRY is scoped to an ActiveModel validation CONTEXT — an
on:among the options it will run under, its own or the declaration's (effective_entry_options) — which makes it permanently inert:Fields.errors_forcallsvalid?with no context, so the entry runs on no call at all and whatever it would have rejected is vacuous. -
.entry_effective_gate_keys(entry_opts, decl_gates) ⇒ Object
Which gate keys EFFECTIVELY gate a single validator entry, given the declaration-level gates (
decl_gates= the sliced :if/:unless off the whole declaration, already blank-canonicalized). -
.entry_effective_option(entry_opts, declaration_options, key) ⇒ Object
The value of one ActiveModel shared default option (
:if/:unless/:on/:strict/…) as it applies to a single validator ENTRY. -
.entry_self_gated?(entry_opts) ⇒ Boolean
Whether an entry carries a gate OF ITS OWN — a non-blank nested if:/unless: that can skip just this validator, whatever the rest of the declaration does.
-
.format_admits_nil?(entry_opts) ⇒ Boolean
Whether a
format:ENTRY would let a nil through. -
.length_admits_nil?(entry_opts) ⇒ Boolean
Whether a
length:entry lets a NIL through — a different question from what sizes it admits. -
.nil_accepted?(validations) ⇒ Boolean
Whether the field's validators, taken together, permit a nil/omitted value.
-
.nil_tolerant_validation?(key, opt, declaration_options) ⇒ Boolean
declaration_optionsare the shared options the entry rides alongside — required rather than defaulted, so a caller cannot omit the tier that decides several of these answers and get a quietly wrong one. -
.normalize_validator_options(value) ⇒ Object
Normalize a scalar validator value the way ActiveModel's own
validatesdoes, so the tolerance push-down (contract.rb_parse_field_validations) can layer allow_blank:/allow_nil: onto the SAME options hashvalidateswould build — the terse spelling (numericality: true,inclusion: [..]/1..5,format: /re/) then combines transparently with a tolerance flag, matching how it behaves WITHOUT one (PRO-2915). -
.set_includes_nil?(opt, keys: %i[in within])) ⇒ Boolean
Tri-state: nil = can't tell; true/false = nil's membership in the set.
-
.shared_validation_option_keys ⇒ Object
ActiveModel's shared "default" validator options — keys that ride alongside validator entries in a
validatescall but are NOT validators themselves (if:/unless:/on:/strict:/allow_blank:/ allow_nil:). -
.type_admits_nil?(entry_opts) ⇒ Boolean
Whether a
type:ENTRY would let a nil through — nil is an instance of at least one declared klass (type: [Array, NilClass],type: Object), so TypeValidator finds the value valid and the nil is no type violation at all. -
.type_klass_admits_nil?(klass) ⇒ Boolean
Whether ONE declared klass would let a nil through, per TypeValidator's own matcher — so the nil-tolerance judgment that drives optional?/requiredness/nullability and the declaration-time nil-skip push-down cannot disagree about the same declaration.
-
.validator_entries(validations) ⇒ Object
The real VALIDATOR entries in a validations hash — everything that is NOT an ActiveModel shared option (if:/unless:/on:/strict:/allow_blank:/allow_nil:).
-
.validator_entry_options(entry) ⇒ Object
ONE validator ENTRY's options as ActiveModel will act on them — the form to read whenever a judgment turns on an entry's CONTENTS.
Instance Method Summary collapse
-
#method_missing(method_name) ⇒ Object
Delegate unknown methods to the action instance so symbol-referenced validation arguments (e.g.
inclusion: { in: :valid_channels_for_number }) resolve against the action — for top-level fields and subfields alike. - #respond_to_missing?(method_name, include_private = false) ⇒ Boolean
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name) ⇒ Object
Delegate unknown methods to the action instance so symbol-referenced validation arguments
(e.g. inclusion: { in: :valid_channels_for_number }) resolve against the action — for
top-level fields and subfields alike.
354 355 356 357 358 359 |
# File 'lib/axn/core/validation/base.rb', line 354 def method_missing(method_name, ...) action = _action_for_validation return super unless action && action.respond_to?(method_name, true) # rubocop:disable Style/SafeNavigation action.send(method_name, ...) end |
Class Method Details
.acceptance_admits_nil?(entry_opts) ⇒ Boolean
Whether an acceptance: ENTRY would let a nil through. ActiveModel's AcceptanceValidator skips a nil
outright unless the entry disables that (allow_nil: false), and even then accepts a value that is a
MEMBER of the accept set — so an explicit nil in the set is accepted with the skip disabled. With no
set of its own AM compares against its default ["1", true], which excludes nil, so the absence of a
set is not tolerance. Membership is the shared literal-set judgment, which answers "unknown" for a set
reflection may not read — and unknown resolves to nil-REJECTING, the safe direction.
161 162 163 164 165 |
# File 'lib/axn/core/validation/base.rb', line 161 def self.acceptance_admits_nil?(entry_opts) return true unless entry_opts.is_a?(Hash) && entry_opts[:allow_nil] == false set_includes_nil?(entry_opts, keys: %i[accept]) == true end |
.declared_length_checks(entry_opts) ⇒ Object
The size checks a length: entry actually runs, resolved the way ActiveModel's LengthValidator
resolves its own options (activemodel 7.2.2.2): in:/within: expand into minimum:/maximum: (a
beginless/endless range contributing only the bound it has, an exclusive end counting one less), and
an entry that is explicitly blank-INtolerant with neither minimum: nor is: of its own picks up
AM's implicit minimum: 1. A falsy bound is dropped, mirroring validate_each's own
next unless check_value = options[key].
THE single definition of "what does this entry check", read by the floor reader below and by the nil-tolerance judgment, so neither can disagree with the other about one declaration.
268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/axn/core/validation/base.rb', line 268 def self.declared_length_checks(entry_opts) opts = (entry_opts) checks = opts.slice(:is, :minimum, :maximum).select { |_key, bound| bound } if (range = opts[:in] || opts[:within]).is_a?(Range) checks[:minimum] = range.min if range.begin checks[:maximum] = (range.exclude_end? ? range.end - 1 : range.end) if range.end end checks[:minimum] = 1 if opts[:allow_blank] == false && checks[:minimum].nil? && checks[:is].nil? checks end |
.declared_length_floor(entry_opts) ⇒ Object
The smallest size a length: entry admits, read from the checks it runs. A maximum: 0 names the
floor too, by leaving size 0 as the only admissible size. Returns nil when the entry leaves the floor
open (a maximum: of 1 or more, or no size key at all), and :unverifiable for a floor AM resolves
per call (a Symbol/Proc).
THE single definition of "how small a value may this length: entry be", shared by the emptiness
reconciliation at declaration (contract.rb) and by schema reflection's minItems/minProperties/
minLength emission, so the runtime floor and the emitted floor cannot disagree.
288 289 290 291 292 293 294 295 296 297 |
# File 'lib/axn/core/validation/base.rb', line 288 def self.declared_length_floor(entry_opts) checks = declared_length_checks(entry_opts) floor = checks[:is] || checks[:minimum] max = checks[:maximum] return 0 if floor.nil? && max.is_a?(Numeric) && max.zero? return :unverifiable unless floor.nil? || floor.is_a?(Numeric) floor end |
.effective_entry_options(entry, declaration_options) ⇒ Object
ONE entry's options as validates will hand them to the validator: the declaration-wide shared options
with the entry's own merged over them, which is literally what AM builds
(defaults.merge(_parse_validates_options(options)), activemodel 7.2.2.2). Every per-entry judgment reads
THIS rather than the entry alone, because a shared allow_nil:/allow_blank:/on:/strict: governs how
the entry runs just as an entry's own does — and an entry's own value overrides the shared one per key,
which the merge order gives for free.
A falsy entry is a disabled validator AM skips outright, so it has no effective options to speak of.
The two GATE questions deliberately do not come through here: entry_effective_gate_keys resolves the
same two tiers with AM's blankness rule applied per key, and entry_self_gated? asks about an entry's own
gate specifically.
59 60 61 62 63 |
# File 'lib/axn/core/validation/base.rb', line 59 def self.(entry, ) return {} unless entry .merge((entry)) end |
.emittable_length_floor?(floor) ⇒ Boolean
Whether a floor read by declared_length_floor is one a JSON Schema minItems/minProperties/
minLength can carry: a positive Integer, the only shape a size constraint takes. THE single
definition of "does this floor count", read by schema reflection's emission AND by the emptiness
reconciliation, which leans on an author's floor only when the schema can advertise the same number —
otherwise the runtime would enforce a floor the schema drops.
Two floors are positive yet uncarryable, and neither can hold the emptiness axis: Float::INFINITY
(ActiveModel's own spelling for "no size passes", which no finite floor expresses) and a fractional
one (which ActiveModel refuses as a bound outright — its LengthValidator accepts a non-negative
Integer, Float::INFINITY, a Symbol or a Proc, and raises otherwise at validation time).
349 |
# File 'lib/axn/core/validation/base.rb', line 349 def self.emittable_length_floor?(floor) = floor.is_a?(Integer) && floor.positive? |
.entry_context_scoped?(entry_opts) ⇒ Boolean
Whether a validator ENTRY is scoped to an ActiveModel validation CONTEXT — an on: among the options it
will run under, its own or the declaration's (effective_entry_options) — which makes it permanently
inert: Fields.errors_for calls valid? with no context, so the entry runs on no call at all and
whatever it would have rejected is vacuous. Only the key's presence is asked, at either tier: validate
installs the context gate on options.key?(:on) whatever the value, so on: nil/false/[] name a
context no call is in exactly as on: :publish does.
Distinct from an if:/unless: GATE, which a given call MAY run: reflection counts a gated entry as if
its gate were open (static-maximal — stricter than a closed-gate runtime, and safe), while a
context-scoped entry has no call on which it applies. THE single definition, shared by the
declaration-time nil-skip push-down, the emptiness axis's deferral test, and schema reflection's
floor emission. (Not to be confused with a DECLARATION-level on:, which is axn's subfield parent
and never reaches a validator entry.)
153 |
# File 'lib/axn/core/validation/base.rb', line 153 def self.entry_context_scoped?(entry_opts) = entry_opts.is_a?(Hash) && entry_opts.key?(:on) |
.entry_effective_gate_keys(entry_opts, decl_gates) ⇒ Object
Which gate keys EFFECTIVELY gate a single validator entry, given the declaration-level gates
(decl_gates = the sliced :if/:unless off the whole declaration, already blank-canonicalized).
Models AM's per-key precedence STRUCTURALLY — which keys are present/blank — never by evaluating
a condition. Blankness is the measured predicate AM applies (value.blank? — a Proc/Symbol is
never blank; nil/false/""/[] are), so a blank nested value drops the shared gate for that key and
is then ignored, leaving the entry UN-gated on it. Declaration-level gates arrive
blank-canonicalized, so a present key there is always a real gate. An entry is EFFECTIVELY GATED
iff any returned key survives.
THE single definition of "can this validator be skipped at runtime", shared by schema reflection
and by the declaration-time nil-skip push-down (contract.rb _type_rejects_nil?), so both judge
the same declaration the same way.
242 243 244 245 246 |
# File 'lib/axn/core/validation/base.rb', line 242 def self.entry_effective_gate_keys(entry_opts, decl_gates) Axn::Internal::FieldConfig::CONDITIONAL_GATE_KEYS.reject do |key| entry_effective_option(entry_opts, decl_gates, key).blank? end end |
.entry_effective_option(entry_opts, declaration_options, key) ⇒ Object
The value of one ActiveModel shared default option (:if/:unless/:on/:strict/…) as it
applies to a single validator ENTRY. validates builds each validator's options as
declaration_defaults.merge(entry_options) (activemodel 7.2.2.2), so an entry that carries the
key OVERRIDES the declaration-level value — including overriding it with a blank/falsy one — and
an entry that does not carries the declaration's. THE single definition of that precedence, so
every question about a shared option ("is this entry gated?", "would it raise?") resolves the
two tiers identically. The QUALIFYING test is the caller's, because it differs per key: gates
turn on blankness, strict: on truthiness.
225 226 227 228 |
# File 'lib/axn/core/validation/base.rb', line 225 def self.entry_effective_option(entry_opts, , key) nested = entry_opts.is_a?(Hash) ? entry_opts : {} nested.key?(key) ? nested[key] : [key] end |
.entry_self_gated?(entry_opts) ⇒ Boolean
Whether an entry carries a gate OF ITS OWN — a non-blank nested if:/unless: that can skip just this validator, whatever the rest of the declaration does. Asked through the per-entry gate model above with NO declaration-level gates supplied, which is what "of its own" means: a declaration-level gate skips every validator together, so it is not this entry's. Blankness is AM's own rule (a blank nested gate is a no-op; a Symbol/Proc is never blank), measured because NESTED gates are not canonicalized at declaration the way declaration-level ones are.
THE single definition of "can this one entry be skipped on its own", shared by the emptiness axis's deferral test and by schema reflection's requiredness-relaxation reasoning.
257 |
# File 'lib/axn/core/validation/base.rb', line 257 def self.entry_self_gated?(entry_opts) = entry_effective_gate_keys(entry_opts, {}).any? |
.format_admits_nil?(entry_opts) ⇒ Boolean
Whether a format: ENTRY would let a nil through. FormatValidator tests value.to_s against the
pattern (activemodel 7.2.2.2), so a nil is tested as the empty string and the pattern decides — with
the polarity flipped by key: with: records an error UNLESS the pattern matches, so it tolerates a nil
exactly when the pattern matches ""; without: records one WHEN it matches, so it tolerates a nil
exactly when the pattern does not. with: is asked first, as AM asks it.
Only a literal Regexp answers: Regexp#match? on one runs no user code, while a Proc/Symbol option is
resolved against the record at validation time (AM's resolve_value) and reflection may never run it
— unknown, which resolves to nil-REJECTING. Exact-class, since a subclass could override match?.
The entry is read in the shape AM acts on, so a bare format: /re/ is judged as the with: it becomes.
309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/axn/core/validation/base.rb', line 309 def self.format_admits_nil?(entry_opts) opts = (entry_opts) if (with = opts[:with]).instance_of?(Regexp) with.match?("") elsif (without = opts[:without]).instance_of?(Regexp) !without.match?("") else false end end |
.length_admits_nil?(entry_opts) ⇒ Boolean
Whether a length: entry lets a NIL through — a different question from what sizes it admits.
ActiveModel compares a nil against one check only: validate_each reaches the comparison for a nil
when skip_nil_check?(key) holds — key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? (activemodel 7.2.2.2) — and a nil measures 0 (nil.to_s.length), which
clears any maximum. Every other check records its error on a nil whatever its bound, so a minimum: 0
rejects a nil even while admitting size 0, and a range rejects one through the floor it sets.
An explicit allow_nil: false/allow_blank: false turns that skip off, and a truthy one never
reaches here (the generic tolerance branch answers first), so the key's presence is what is asked —
mirroring skip_nil_check?, which is private to the validator instance.
331 332 333 334 335 336 337 |
# File 'lib/axn/core/validation/base.rb', line 331 def self.length_admits_nil?(entry_opts) opts = (entry_opts) return false unless opts[:allow_nil].nil? && opts[:allow_blank].nil? checks = declared_length_checks(opts) checks.key?(:maximum) && !checks.key?(:minimum) && !checks.key?(:is) end |
.nil_accepted?(validations) ⇒ Boolean
Whether the field's validators, taken together, permit a nil/omitted value. Drives both input optionality and nullability (adding "null" to the emitted type). A lone validator's allow_nil: doesn't count if another (presence, type, …) still rejects nil.
An entry is nil-tolerant if it's a disabled validator (falsy opt — false or nil, both of
which ActiveModel skips), one scoped to a validation context (it never runs, so it rejects
nothing), absence (nil is always
"absent"), acceptance unless explicitly allow_nil: false (ActiveModel's acceptance is allow_nil
by default), a Hash allowing nil/blank, confirmation (ActiveModel compares only when the
<attr>_confirmation accessor is non-nil, so the check adds no error of its own on a nil), a
maximum-only length: (the one check ActiveModel compares a nil against, and a nil's measured size of
0 clears any maximum — see length_admits_nil?), a format: whose literal pattern admits the empty
string a nil is tested as (see format_admits_nil?), a type: at least one of whose declared klasses nil
is an instance of (TypeValidator then reports no defect, so the nil is no type violation at all), an
exclusion set not containing nil, or an inclusion set that explicitly contains nil. Any other
active validator — including a bare true (e.g. numericality: true) — rejects nil.
This is the question requiredness and nullability both turn on, asked identically by schema
reflection and by a field config's own optional? so the two can never disagree about the same
declaration.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/axn/core/validation/base.rb', line 101 def self.nil_accepted?(validations) # Judge only the REAL validators: ActiveModel's shared options (if:/unless:/on:/strict:/ # allow_blank:/allow_nil:) ride in the validations hash but aren't validators, so a restored # `strict: true` under a tolerance flag must not read as a nil-rejecting validator and wrongly # mark the field required. The judgment is static-maximal: gated validators are counted as if # their gates were open (a condition can only relax enforcement at runtime, never tighten it) — a # context-scoped entry is different in kind, running on no call at all, and counts for nothing. v = validator_entries(validations) return true if v.empty? # The shared options are stripped from the ENTRY set but still govern how each entry runs, so they are # handed to the per-entry judgment rather than discarded: `validates` applies a declaration-wide # `allow_nil:`/`allow_blank:` to every validator in the call. = validations.slice(*shared_validation_option_keys) v.all? { |key, opt| nil_tolerant_validation?(key, opt, ) } end |
.nil_tolerant_validation?(key, opt, declaration_options) ⇒ Boolean
declaration_options are the shared options the entry rides alongside — required rather than defaulted,
so a caller cannot omit the tier that decides several of these answers and get a quietly wrong one.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/axn/core/validation/base.rb', line 120 def self.nil_tolerant_validation?(key, opt, ) return true unless opt # a disabled validator (falsy `opt` — `false`/`nil`); ActiveModel skips it # Judged on the options `validates` will actually hand the validator, so a declaration-wide tolerance or # context counts exactly as an entry's own does. opts = (opt, ) return true if entry_context_scoped?(opts) return true if opts[:allow_nil] || opts[:allow_blank] return true if key == :absence return true if key == :acceptance && acceptance_admits_nil?(opts) return true if key == :confirmation return true if key == :format && format_admits_nil?(opts) return true if key == :length && length_admits_nil?(opts) return true if key == :type && type_admits_nil?(opts) return true if key == :exclusion && set_includes_nil?(opts) == false return true if key == :inclusion && set_includes_nil?(opts) == true false end |
.normalize_validator_options(value) ⇒ Object
Normalize a scalar validator value the way ActiveModel's own validates does, so the tolerance
push-down (contract.rb _parse_field_validations) can layer allow_blank:/allow_nil: onto the
SAME options hash validates would build — the terse spelling (numericality: true,
inclusion: [..]/1..5, format: /re/) then combines transparently with a tolerance flag,
matching how it behaves WITHOUT one (PRO-2915). Reuses AM's private _parse_validates_options
rather than copying its case statement, so the mapping cannot drift (activemodel 7.2.2.2:
TrueClass→{}, Hash→itself, Range/Array→in:, else→with:).
29 |
# File 'lib/axn/core/validation/base.rb', line 29 def self.(value) = (value) |
.set_includes_nil?(opt, keys: %i[in within])) ⇒ Boolean
Tri-state: nil = can't tell; true/false = nil's membership in the set. Only inspected for in-memory
literal collections: reflection must stay side-effect-free, so a dynamic collection (e.g. an
ActiveRecord::Relation, whose include? would query the database) is treated as unknown (nil).
Detection is identity-based (equal?(nil)), never include?/==: an element with a custom ==
could itself run user code. A Range's bounds are Comparable, so nil is never a member.
rubocop:disable Style/ReturnNilInPredicateMethodDefinition
keys: names where the set lives in the long form, so the one judgment serves every validator that
compares a value against a literal set — in:/within: for inclusion/exclusion, accept: for
acceptance.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/axn/core/validation/base.rb', line 199 def self.set_includes_nil?(opt, keys: %i[in within]) # The set is the collection under one of `keys` (hash long form) or the bare collection itself # (shorthand — inclusion: %w[a b], exclusion: [nil, "x"]); the two are equivalent at runtime, so # nil-membership is judged the same for both. collection = opt.is_a?(Hash) ? keys.filter_map { |key| opt[key] }.first : opt return false if collection.is_a?(Range) # A Hash is a collection ActiveModel accepts, and its `include?` tests KEYS — so the keys are the # members whose nil-membership is asked, judged by the same identity rule as any other set. members = collection.instance_of?(Hash) ? collection.keys : collection return nil unless members.instance_of?(Array) || (defined?(Set) && members.instance_of?(Set)) members.any? { |element| element.equal?(nil) } rescue StandardError nil end |
.shared_validation_option_keys ⇒ Object
ActiveModel's shared "default" validator options — keys that ride alongside validator entries
in a validates call but are NOT validators themselves (if:/unless:/on:/strict:/allow_blank:/
allow_nil:). Exposed so the tolerance push-down (contract.rb) can hold them OUT of the
per-validator scalar normalization — merging tolerance into strict: true, say, would rewrite
it to a Hash and break strict raising. Reuses AM's own canonical list so the set can't drift.
70 |
# File 'lib/axn/core/validation/base.rb', line 70 def self.shared_validation_option_keys = _validates_default_keys |
.type_admits_nil?(entry_opts) ⇒ Boolean
Whether a type: ENTRY would let a nil through — nil is an instance of at least one declared klass
(type: [Array, NilClass], type: Object), so TypeValidator finds the value valid and the nil is no
type violation at all. Union semantics are TypeValidator's own: a value matching ANY declared klass
passes, so one nil-admitting member admits nil.
171 172 173 174 |
# File 'lib/axn/core/validation/base.rb', line 171 def self.type_admits_nil?(entry_opts) klasses = Array(entry_opts.is_a?(Hash) ? entry_opts[:klass] : entry_opts) klasses.any? { |klass| type_klass_admits_nil?(klass) } end |
.type_klass_admits_nil?(klass) ⇒ Boolean
Whether ONE declared klass would let a nil through, per TypeValidator's own matcher — so the nil-tolerance judgment that drives optional?/requiredness/nullability and the declaration-time nil-skip push-down cannot disagree about the same declaration.
179 180 181 182 183 184 185 186 187 |
# File 'lib/axn/core/validation/base.rb', line 179 def self.type_klass_admits_nil?(klass) Validators::TypeValidator.value_matches?(nil, klass:) rescue TypeError # A `type:` that is neither a Class/Module nor a known pseudo-type is a broken declaration whose # TypeError belongs to validation time, where it already surfaces. Answer "admits nil" so the caller # stands down and the declaration behaves exactly as it does with the question unasked, rather than # preempting that error here. Scoped to this one call so an unrelated TypeError still propagates. true end |
.validator_entries(validations) ⇒ Object
The real VALIDATOR entries in a validations hash — everything that is NOT an ActiveModel shared
option (if:/unless:/on:/strict:/allow_blank:/allow_nil:). THE single definition of "is this a
validator", shared by the validator-class builder, the gate sweeps, and schema reflection, so
"does this field have any validators / do its validators accept nil" is decided one way
everywhere. Without it, a shared-only hash like { strict: true } reads as a validator: the
builder calls validates and ActiveModel raises "You need to supply at least one validation",
and reflection marks the (omittable) field required.
79 |
# File 'lib/axn/core/validation/base.rb', line 79 def self.validator_entries(validations) = validations.except(*shared_validation_option_keys) |
.validator_entry_options(entry) ⇒ Object
ONE validator ENTRY's options as ActiveModel will act on them — the form to read whenever a
judgment turns on an entry's CONTENTS. A falsy entry is a disabled validator AM skips, which names
nothing at all; every other value is normalized the way validates does, so a bare shorthand
(length: 2..5, inclusion: %w[a b]) is read in the form AM expands it to rather than the form the
author happened to type.
Normalizing at the READ is the point: entries are also normalized in place by the declaration-time passes (the tolerance push-down, the nil-skip), but those run only under their own conditions — a gated or nil-admitting type entry leaves its siblings exactly as written — so a reader that assumed a Hash would silently skip a constraint ActiveModel enforces. Idempotent on a Hash, so asking here costs nothing where a pass already ran.
42 43 44 45 46 |
# File 'lib/axn/core/validation/base.rb', line 42 def self.(entry) return {} unless entry (entry) end |
Instance Method Details
#respond_to_missing?(method_name, include_private = false) ⇒ Boolean
361 362 363 364 365 366 |
# File 'lib/axn/core/validation/base.rb', line 361 def respond_to_missing?(method_name, include_private = false) action = _action_for_validation return super unless action action.respond_to?(method_name, include_private) || super end |