Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

request.format in rescue_from block

I have a rails controller with a rescue_from block in which I call render.

class SomeController < ApplicationController
  rescue_from Some::Error, :some_error

  private

  def some_error error
    @error = error
    render 'error'
  end
end

The strange thing is that even if I have an error.js.erb view, rails will always use error.html.erb, even if the request is a JS:

Started GET /some/1
Processing by SomeController#show as JS
...
Rendered some/error.html.erb

Not how in the shortened log above it says it's rendering as JS, but it still uses the HTML file. The .js.erb is at the right location, and rendering JS views when there's no rescue_from involved works just fine.

What's going on here?

Update 1: I have created a test repository to demonstrate the problem

Update 2 I've found a solution (see below). Can anyone come up with a more generic solution like the ones below, or can you tell my why this would be impossible or a really bad idea? The bounty is still open.

  • Would it make sense to create a pull request to set self.formats in ActionController::Rescue.process_action or
  • Get really crazy and try to resume the stack one level deeper than where the error was raised
like image 445
amiuhle Avatar asked Jul 24 '15 14:07

amiuhle


1 Answers

Solution

I did some debugging and digging around in the rails sources and found a solution myself:

def error
  @error = error
  self.formats = request.formats.map(&:ref).compact
  render 'error'
end

Explanation

Calling the rescue_from block happens in ActionController::Rescue.process_action. If there is an error, the block will be called. If there is no error, eventually ActionController::Rendering.process_action will get called which just sets self.formats:

def process_action(*) #:nodoc:
  self.formats = request.formats.map(&:ref).compact
  super
end

Here is a full stacktrace between ActionController::Rescue.process_action and the actual controller action.

#0  TestController.index at /tmp/rescue_from/app/controllers/test_controller.rb:11
#1  ActionController::ImplicitRender.send_action(method#String, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/implicit_render.rb:4
#2  AbstractController::Base.process_action(action#NilClass, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/abstract_controller/base.rb:198
#3  ActionController::Rendering.process_action(action, *args) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/rendering.rb:10
#4  block in AbstractController::Callbacks.process_action(action#NilClass, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/abstract_controller/callbacks.rb:20
ͱ-- #5  Proc.call(*args) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/callbacks.rb:115
#6  ActiveSupport::Callbacks::Filters::End.call(env#ActiveSupport::Callbacks::Filters::Environment) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/callbacks.rb:115
#7  block (2 levels) in ActiveSupport::Callbacks::CallbackChain.compile at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/callbacks.rb:553
ͱ-- #8  Proc.call(*args) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/callbacks.rb:503
#9  ActiveSupport::Callbacks::CallbackSequence.call(*args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/callbacks.rb:503
#10 ActiveSupport::Callbacks.run_callbacks(kind#Symbol, &block#Proc) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/callbacks.rb:88
#11 AbstractController::Callbacks.process_action(action#NilClass, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/abstract_controller/callbacks.rb:19
#12 ActionController::Rescue.process_action(action#NilClass, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/rescue.rb:29

Here is a full stacktrace when there is an error (thrown in a before_action):

--> #0  TestController.standard_error(error#RuntimeError) at /Users/timou/tmp/rescue_from/app/controllers/test_controller.rb:20
ͱ-- #1  Method.call(*args) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/rescuable.rb:80
#2  ActiveSupport::Rescuable.rescue_with_handler(exception#RuntimeError) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activesupport-4.2.3/lib/active_support/rescuable.rb:80
#3  ActionController::Rescue.rescue_with_handler(exception#RuntimeError) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/rescue.rb:15
#4  rescue in ActionController::Rescue.process_action(action#NilClass, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/rescue.rb:32
#5  ActionController::Rescue.process_action(action#NilClass, *args#Array) at /usr/local/Cellar/rbenv/0.4.0/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/rescue.rb:29

So my problem was actually that the error is raised in a before_action, which is handled by AbstractController::Callbacks.process_action and happens before ActionController::Rendering.process_action can set self.formats.

If the error is raised in the action itself, self.formats would already be set and the correct view would be rendered without settings self.formats in the rescue_from block.

like image 50
amiuhle Avatar answered Oct 06 '22 22:10

amiuhle