Introduce Grape::Validations::CoerceOptions value object#2722
Open
ericproulx wants to merge 1 commit into
Open
Introduce Grape::Validations::CoerceOptions value object#2722ericproulx wants to merge 1 commit into
ericproulx wants to merge 1 commit into
Conversation
`ValidationsSpec#coerce_options` returned an ad-hoc
`{ type:, method:, message: }` Hash that flowed (deep-frozen) into
`CoerceValidator` as its `options` argument and was poked at by
`ParamsScope#check_coerce_with` / `#validate_coerce`. Unlike most
validator options, this Hash is *never written by the user* — it is
assembled internally from the parsed `type:` / `coerce_with:` /
`coerce_message` declaration — so it has a fixed shape and no public
contract to preserve.
Replace it with a `Grape::Validations::CoerceOptions` Data value object
(`Data.define(:type, :coerce_method, :message)`, all defaulting nil).
The member is `coerce_method`, not `method`, to avoid shadowing
`Object#method` (`Lint/DataDefineOverride`); it also matches
`ValidationsSpec`'s existing `coerce_method` vocabulary.
- `ValidationsSpec#coerce_options` builds the value object.
- `ParamsScope#check_coerce_with` / `#validate_coerce` read `.type` /
`.coerce_method` instead of `[:type]` / `[:method]`.
- `CoerceValidator` reads `@options.type` / `@options.coerce_method`.
`Base#message` can't see a custom message off a Data (it probes
Hash-like `key?`), so `CoerceValidator#initialize` restores
`@exception_message` from `@options.message`, preserving
`type: { value:, message: }` behaviour.
`Grape::Util::DeepFreeze` returns the Data as-is (its `else` branch);
`Base.new` already freezes the whole validator instance, and the Data
is structurally immutable. No user-facing contract changes — users
never construct or read this Hash; no spec hand-builds it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90b651e to
985d739
Compare
Danger ReportNo issues found. |
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.
Summary
ValidationsSpec#coerce_optionsreturned an ad-hoc{ type:, method:, message: }Hash that flowed (deep-frozen) intoCoerceValidatoras itsoptionsargument and was poked at byParamsScope#check_coerce_with/#validate_coerce.Unlike most validator options (which are a genuine union — bare value,
{ value:, message: }envelope, or structured Hash, depending on what the user wrote), this one is never written by the user. It's assembled internally from the parsedtype:/coerce_with:/coerce_messagedeclaration, so it has a fixed shape and no public contract to preserve — the cleanest possibleData.definecandidate.Replace it with
Grape::Validations::CoerceOptions:The member is
coerce_method, notmethod— defining:methodwould shadowObject#method(Lint/DataDefineOverride), andcoerce_methodalready matchesValidationsSpec's existing accessor name.Changes
ValidationsSpec#coerce_optionsbuilds the value object.ParamsScope#check_coerce_with/#validate_coerceread.type/.coerce_methodinstead of[:type]/[:method].CoerceValidatorreads@options.type/@options.coerce_method.Base#messageresolves a custom message by probing a Hash-likekey?, which a Data doesn't answer, soCoerceValidator#initializerestores@exception_messagefrom@options.message— preservingtype: { value: Integer, message: 'bad' }behaviour (covered by existing specs).Why it's safe
Grape::Util::DeepFreezereturns the Data via itselsebranch unchanged;Base.newalready freezes the whole validator instance, and Data is structurally immutable.CoerceValidatorwith it (the validator specs go through the fullGrape::APIDSL).return unless coerce_options.typeguard invalidate_coerceis preserved (a base instance with no resolved type still skips).Test plan
bundle exec rspec— 2307 examples, 0 failures (incl.coerce_validator_spec97/0, custom-message path)Lint/DataDefineOverride)🤖 Generated with Claude Code