Skip to content

Add a numericality validator#2822

Closed
ericproulx wants to merge 1 commit into
masterfrom
add_numericality_validator
Closed

Add a numericality validator#2822
ericproulx wants to merge 1 commit into
masterfrom
add_numericality_validator

Conversation

@ericproulx

Copy link
Copy Markdown
Contributor

Summary

Grape currently has no built-in validator for bounding or shape-checking a numeric parameter beyond values: (a closed inclusive range or an explicit list/predicate). Common cases like "must be a positive integer", "0 to 100 with an exclusive upper bound", or "must be a perfect score of exactly 5" require either abusing values: with a lambda (weak error messages, awkward for open-ended bounds) or writing a full contract_scope schema for a single field.

This PR adds a numericality validator, following ActiveModel::Validations::NumericalityValidator's option names for familiarity to anyone coming from Rails:

params do
  requires :quantity, type: Integer, numericality: { greater_than: 0 }
  requires :discount, type: Float, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
  requires :rating,   type: Integer, numericality: { equal_to: 5 }
  requires :page,     type: Integer, numericality: { greater_than: 0, only_integer: true }
end

Supported options (any combination): greater_than, greater_than_or_equal_to, less_than, less_than_or_equal_to, equal_to, other_than, only_integer, odd, even. Each has its own i18n message key (numericality_greater_than, etc.), mirroring how length already has separate length_min/length_max/length_is keys. Per-field message: overrides work the same way as every other validator.

Design notes

  • Non-Numeric values are skipped, not errored. Numeric-ness is type:/coercion's job; this validator only owns bounds/parity, so it composes with coerce rather than duplicating its work. A param with no type:/coerce still round-trips through unchanged if it isn't already a Numeric.
  • Typed arrays are checked element-wise. requires :numbers, type: [Integer], numericality: { greater_than: 0 } validates every element (via Array.wrap), matching how values already treats arrays. length is the one validator that intentionally owns the array's own size, so no overlap there.
  • only_integer accepts whole-numbered Float/BigDecimal, not just literal Integerval == val.to_i — so type: Float, numericality: { only_integer: true } accepts 5.0 and rejects 5.5.
  • Obviously contradictory combinations raise ArgumentError at definition time, not at request time: greater_than + greater_than_or_equal_to, less_than + less_than_or_equal_to, odd + even, equal_to + any other comparison option, and a reversed lower/upper bound (e.g. greater_than: 10, less_than: 5). This mirrors LengthValidator's existing min > max guard.

No existing validator's behavior changes; this is purely additive (new validator class, new locale keys, new README section). No UPGRADING.md entry needed.

Test plan

  • New spec/grape/validations/validators/numericality_validator_spec.rb (34 examples): every option's pass/fail case, the range combination, the only_integer Float case, odd/even, array-element-wise checking, non-Numeric skip behavior, allow_blank interplay, custom message: override, and every ArgumentError guard.
  • Full RSpec suite passes locally (2401 examples, 0 failures).
  • RuboCop clean across the repo.
  • CI green.

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the add_numericality_validator branch from 7b119a1 to 8e72ff1 Compare July 26, 2026 14:52
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@ericproulx
ericproulx requested a review from dblock July 26, 2026 15:05
Grape has no built-in way to bound or shape-check a numeric parameter
beyond `values:` (a closed inclusive range or list). Users end up
reaching for a `values:` lambda or a full `contract_scope` schema for
something as simple as "must be a positive integer" or "0..100
inclusive with an exclusive upper bound", both of which are more
ceremony than the check deserves and produce weak error messages.

Add `NumericalityValidator`, registered as `numericality`, following
ActiveModel::Validations::NumericalityValidator's option naming for
familiarity: `greater_than`, `greater_than_or_equal_to`, `less_than`,
`less_than_or_equal_to`, `equal_to`, `other_than`, `only_integer`,
`odd`, `even`. Any combination can be supplied and each has its own
i18n message key (mirroring how `length` has separate `length_min`/
`length_max`/`length_is` keys).

Design notes:
- Non-Numeric values are skipped rather than erroring — numeric-ness
  is `type:`/coercion's job; this validator only owns bounds/parity,
  so it composes with `coerce` instead of duplicating it.
- Applied to a typed array (`type: [Integer]`), every element is
  checked individually (`Array.wrap`), matching how `values` already
  treats arrays — `length` is the one that owns the array's own size.
- `only_integer` accepts whole-numbered `Float`/`BigDecimal` too
  (`val == val.to_i`), not just literal `Integer`.
- Obviously contradictory option combinations (`greater_than` +
  `greater_than_or_equal_to`, `less_than` + `less_than_or_equal_to`,
  `odd` + `even`, `equal_to` + any other comparison, or a reversed
  lower/upper bound) raise `ArgumentError` at definition time, same
  as `length_validator`'s existing `min > max` guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the add_numericality_validator branch from 8e72ff1 to 969e0f2 Compare July 26, 2026 15:07
@dblock

dblock commented Jul 26, 2026

Copy link
Copy Markdown
Member

Supporting open ranges would be cleaner because we already support closed ranges, no?

params do
  requires :quantity, type: Integer, values: 1.. # numericality: { greater_than: 0 }
  requires :discount, type: Float, values: 0..100 # numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 }
  requires :rating,   type: Integer, values: 5 # numericality: { equal_to: 5 }
  requires :page,     type: Integer, values: 1.. # this one has to be integer because of type ... numericality: { greater_than: 0, only_integer: true }
end

@ericproulx

Copy link
Copy Markdown
Contributor Author

Yeah, makes sense. I'll drop it.

@ericproulx ericproulx closed this Jul 26, 2026
@ericproulx
ericproulx deleted the add_numericality_validator branch July 26, 2026 19:04
@dblock

dblock commented Jul 27, 2026

Copy link
Copy Markdown
Member

Yeah, makes sense. I'll drop it.

Will you add support for open ranges? It's a useful feature.

Copilot is working ...

dblock added a commit to dblock/grape that referenced this pull request Jul 27, 2026
…idator

Instead of adding a new numericality validator (ruby-grape#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: ruby-grape#2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dblock added a commit to dblock/grape that referenced this pull request Jul 27, 2026
…idator

Instead of adding a new numericality validator (ruby-grape#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: ruby-grape#2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dblock added a commit to dblock/grape that referenced this pull request Jul 27, 2026
…idator

Instead of adding a new numericality validator (ruby-grape#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: ruby-grape#2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dblock added a commit to dblock/grape that referenced this pull request Jul 27, 2026
…idator

Instead of adding a new numericality validator (ruby-grape#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: ruby-grape#2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dblock added a commit to dblock/grape that referenced this pull request Jul 27, 2026
…idator

Instead of adding a new numericality validator (ruby-grape#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: ruby-grape#2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dblock added a commit to dblock/grape that referenced this pull request Jul 27, 2026
…idator

Instead of adding a new numericality validator (ruby-grape#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: ruby-grape#2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dblock

dblock commented Jul 27, 2026

Copy link
Copy Markdown
Member

#2831

ericproulx pushed a commit that referenced this pull request Jul 27, 2026
…idator (#2831)

Instead of adding a new numericality validator (#2822), extend test
coverage and documentation for the existing :values option, which
already supports open (beginless/endless) Ranges via Range#include?.
This covers the same common cases (positive numbers, percentages,
exact values, and array element-wise checks) without a new validator.

Ref: #2822 (comment)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants