Translate ValidationErrors#full_messages once instead of on every call - #2934
Closed
ericproulx wants to merge 1 commit into
Closed
Translate ValidationErrors#full_messages once instead of on every call#2934ericproulx wants to merge 1 commit into
ericproulx wants to merge 1 commit into
Conversation
ValidationErrors builds its #message from #full_messages when it is
created, and #full_messages translated every error again on each call:
an I18n lookup for the format and one per attribute name, a few
microseconds apiece. The README's recipe for answering with the list,
rescue_from Grape::Exceptions::ValidationErrors do |e|
error!({ messages: e.full_messages }, 400)
end
therefore paid for every translation twice. The list is now kept from
the first call and handed out as a copy, so a caller changing it cannot
change what the next caller gets; a spec pins that.
The list now stays what #message was built from. Before, a handler that
switched the locale and then asked for it got the format and attribute
names in the new locale around messages still in the old one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
perf/memoize-validation-full-messages
branch
from
September 11, 2026 09:36
66cae11 to
08245fd
Compare
Danger ReportErrors
Markdowns* [#XXXX](https://github.com/ruby-grape/grape/pull/XXXX): Translate `ValidationErrors#full_messages` once instead of on every call - [@ericproulx](https://github.com/ericproulx).
does not include a pull request link |
This was referenced Sep 11, 2026
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.
Summary
ValidationErrorsbuilds its#messagefrom#full_messageswhen it is created.#full_messagesthen translated every error again on each call: an I18n lookup for thegrape.errors.formatstring (with interpolation) and one per attribute name. Each is a few microseconds on I18n 1.15.The README's recipe for answering with the list, below, therefore paid for every translation twice:
The list is now kept from the first call and handed out as a copy, so a caller changing it cannot change what the next caller gets.
#messageis still built through#full_messages, so a subclass overriding it keeps working as before.Benchmarks
Median of 7 interleaved subprocess rounds, Ruby 4.0.6. The API uses the README handler above, with
requires :id, type: Integerandrequires :name, type: String.Behaviour
The list now stays what
#messagewas built from. Before, a handler that switched the locale and then asked for the list got a mix of both locales. With a French format and a Frenchpresencemessage,I18n.with_locale(:fr) { e.full_messages }answered["id : is missing", "nick : is missing"]for an error raised in English: the French format and untranslated attribute names around the English messages, which were translated when eachValidationwas raised. It now answers["id is missing", "Nickname is missing"], the same text as#message. I did not add an UPGRADING entry, since the old output was half-translated anyway; happy to add one.Otherwise responses are byte-identical to master over:
rescue_from ValidationErrorshandlers readingfull_messages(once, twice, and after changing the list),message, both together,as_json,to_json,errors,error!(e), and re-raising. It includes translated attribute names, a request in another locale,mutually_exclusive(several params in one error) andvalues. The locale-switching handler above is the only difference.Missing spec, added
A caller changing the array
#full_messagesreturned must not change the next answer. The spec passes on master, where each call builds a new array, and fails if the copy is dropped.Test plan
🤖 Generated with Claude Code