Validate the elements of an Array params scope without the attributes iterator - #2933
Closed
ericproulx wants to merge 1 commit into
Closed
ericproulx wants to merge 1 commit into
ericproulx wants to merge 1 commit into
Conversation
ericproulx
force-pushed
the
perf/array-element-validation-fast-path
branch
from
September 11, 2026 09:20
6332e9e to
9a7ec1c
Compare
Danger ReportNo issues found. |
… iterator A validator on `requires :items, type: Array do ... end` (or `optional`) went through the AttributesIterator for every element: an index bookkeeping call, a yield per attribute, an emptiness test, and per attribute the scope's `required?` and `meets_dependency?`. When the scope depends on no other param, every scope above it is always validated, and it is the only one on the chain that iterates elements, those answers are fixed: its params are the request's Array as it came in, and the iterator only contributes the element index the error names carry and, for an optional scope, passing over empty elements. `Validators::Base#validate_elements!` covers that case the way #2927's `validate_attributes!` covers a Hash scope. `ParamsScope#validated_when_given?` names the condition, and `always_validated?` is that plus being required; a required scope like that also skips `should_validate?`, which always answers true for it. Nested Array scopes and `given` blocks keep the iterator. Adds three specs for behaviour nothing pinned, each found by a mutation that passed the suite: an optional Array scope skips its empty elements, it is not validated at all when every element is blank, and a Hash scope given an Array reports only itself, not the optional Array scope inside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
perf/array-element-validation-fast-path
branch
from
September 11, 2026 09:54
9a7ec1c to
1fdd620
Compare
5 tasks
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #2927 (it builds on
ParamsScope#always_validated?and followsvalidate_attributes!); this PR targets that branch, so the diff shows only this change.Summary
A validator on
requires :items, type: Array do ... end, oroptional, went through theAttributesIteratorfor every element. That meant an index bookkeeping call, a yield per attribute, an emptiness test, and, per attribute, the scope'srequired?andmeets_dependency?.Those answers are fixed when three things hold:
Its params are then the request's Array as it came in. The iterator only contributes the element index that the error names carry (
items[3][id] is missing) and, for an optional scope, passing over empty elements.Validators::Base#validate_elements!covers that case the way #2927'svalidate_attributes!covers a Hash scope:map_paramsputs where the scope's params were missing.required? || (hash_like?(element) && element.key?(attr_name)), collecting the errors in the same order.ParamsScope#validated_when_given?names the condition, andalways_validated?is now that plus being required. A required scope like that also skipsshould_validate?, which always answers true for it. An optional one still asks, because its "every element is blank" check usesblank?, which also coversfalseand whitespace. Nested Array scopes andgivenblocks keep the iterator.Benchmarks
Median of interleaved subprocess rounds, Ruby 4.0.6. POST with a JSON body
{"items": [...]}, where each element holdsrequires :id, type: Integer,optional :name, type: Stringandoptional :qty, type: Integer, values: 1..100.requires, 1 elementrequires, 10 elementsrequires, 50 elementsoptional, 10 elementsPaths without an Array scope are flat: JSON body −0.3%, nested Hash body −0.8%, query params +0.4% (another run: −1.7%, with base-vs-base at −0.8%). The noise floor today is about ±1.6%.
Behaviour
Byte-identical to master and to #2927 over 164 requests: 82 cases against both the Hash and the HashWithIndifferentAccess params builder. They cover:
as:,default:, andmutually_exclusive;giveninside an element;Array[JSON], and Arrays of Arrays;fail_fast, an Array inside a Hash, and awithgroup inside an Array;nil,false, blank Strings, numbers, nested Arrays), a Hash or a String where the Array should be, and query-string Arrays.The 104-case validation matrix from #2927 is also byte-identical.
Missing specs, added
Each of these passed the whole suite when mutated. The new specs pass on master and on this branch:
[{}, {not_key: 'foo'}]reports onlyitems[1][key] is missing.[false, ' ']passes, becauseshould_validate?usesblank?.meta: [{}]answersmeta is invalidand nothing about the optional Array scope inside it.Test plan
required?, or validating absent optional attributes;hash_like?guard, or the Array guard;nilas empty;should_validate?;validated_when_given?ignoring the dependency, or the parent chain.🤖 Generated with Claude Code