amiuhle
amiuhle

Reputation: 2783

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.

Upvotes: 6

Views: 778

Answers (1)

amiuhle
amiuhle

Reputation: 2783

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.

Upvotes: 6

Related Questions