diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc7d3a3e..d2d34b2d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2918](https://github.com/ruby-grape/grape/pull/2918): Skip the dry-types round trip when a value already is the declared type - [@ericproulx](https://github.com/ericproulx). * [#2917](https://github.com/ruby-grape/grape/pull/2917): Read path captures out of the router's union match instead of re-running the route's pattern - [@ericproulx](https://github.com/ericproulx). * [#2921](https://github.com/ruby-grape/grape/pull/2921): Pin the router's request-time isolation regressions through requests instead of its instance variables - [@ericproulx](https://github.com/ericproulx). +* [#2934](https://github.com/ruby-grape/grape/pull/2934): Translate `ValidationErrors#full_messages` once instead of on every call - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 4.0.0 (2026-09-07) diff --git a/lib/grape/exceptions/validation_errors.rb b/lib/grape/exceptions/validation_errors.rb index 9fad6f94c..71a0723a7 100644 --- a/lib/grape/exceptions/validation_errors.rb +++ b/lib/grape/exceptions/validation_errors.rb @@ -23,7 +23,19 @@ def to_json(*_opts) as_json.to_json end + # Translated once, when the error is built for its #message, and handed + # out as a copy from then on. Every lookup here is an I18n call of a few + # microseconds, and the README's recipe for answering with the list, + # +error!({ messages: e.full_messages }, 400)+, asked for all of them a + # second time. It also keeps the list the same as #message, which was + # already fixed at that point, if the locale changes in between. def full_messages + (@full_messages ||= translate_full_messages).dup + end + + private + + def translate_full_messages messages = errors.flat_map do |attributes, errs| errs.map do |error| translate( @@ -39,8 +51,6 @@ def full_messages messages end - private - def translate_attributes(keys) keys.map do |key| translate(key, scope: 'grape.errors.attributes', default: key.to_s) diff --git a/spec/grape/exceptions/validation_errors_spec.rb b/spec/grape/exceptions/validation_errors_spec.rb index 8c0839f9b..a80416fc2 100644 --- a/spec/grape/exceptions/validation_errors_spec.rb +++ b/spec/grape/exceptions/validation_errors_spec.rb @@ -65,6 +65,15 @@ expect(subject.first).to eq('admin_field Can not set admin-only field') end end + + context 'when the caller changes the array it was given' do + subject(:error) { described_class.new(exceptions: [Grape::Exceptions::Validation.new(params: ['id'], message: :presence)]) } + + it 'returns the same messages the next time' do + error.full_messages << 'name is missing' + expect(error.full_messages).to eq(['id is missing']) + end + end end context 'api' do