I am trying to output localized messages to the user inside all rescue_from blocks, but can’t get it to work. I want to avoid the boilerplate of wrapping each handler inside a I18n.with_locale block manually. My general approach is this:
class ApplicationController < ActionController::Base
def self.rescue_from_with_locale(*, **, &)
self.rescue_from(*, **) { I18n.with_locale(params[:locale], &) }
end
rescue_from_with_locale Error1 do
render(plain: t("error1_happened"))
end
rescue_from_with_locale Error2 do
render(plain: t("error2_happened"))
end
end
However, when such an error triggers, Rails raises
NoMethodError (undefined method 't' for class ApplicationController)
So obviously this happens because the original handler is executed in the class context but is supposed to be executed in the instance context instead. The question is how can I ensure the entire handler is executed in the instance context? I found no way of doing so.
I’d keep I18n.with_locale, but call the original handler with instance_exec inside it. Yielding a previously captured block does not change that block’s self; Rails changes the context of your outer wrapper, not of the original handler passed through &.
For the block-only helper in your example:
def self.rescue_from_with_locale(*exceptions, &handler)
rescue_from(*exceptions) do |exception|
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale) do
if handler.arity == 0
instance_exec(&handler)
else
instance_exec(exception, &handler)
end
end
end
end
Here the outer block already runs on the controller instance, so instance_exec also makes that instance the receiver of t and render in the original block. The arity check preserves both zero-argument handlers and handlers receiving the exception. This example intentionally handles block registrations, rather than the with: form.
Also validate the requested locale against the locales your application supports, so an invalid parameter does not raise another exception inside the error handler.