rails: Setting self.response_body = nil no longer prevents DoubleRenderError

Steps to reproduce

I am attempting to prevent a AbstractController::DoubleRenderError in a controller by setting self.response_body = nil before a second call to render. The reason that multiple renders occur is that I have after_action callbacks that call render after the action itself has already rendered its response.

It is my understanding that self.response_body = nil should drop the previously-rendered body to allow a subsequent render call to succeed without causing a DoubleRenderError.

This works in Rails 4.2.6 but it no longer appears prevent double rendering exceptions in Rails 5+ (including current master).

This simplified example shows a scenario that properly renders ‘something else’ in Rails 4.2.6 but fails in 5.0.0.rc1 (and master):

# Rails 4.2.6
def should_prevent_double_render
  render json: { foo: 'something' }
  self.response_body = nil
  performed? # => false
  render json: { foo: 'something else' }
end

# Rails 5.0.0.rc1
def should_prevent_double_render
  render json: { foo: 'something' }
  self.response_body = nil
  performed? # => ["{\"foo\":\"something\"}"]
  render json: { foo: 'something else' }
  # ... AbstractController::DoubleRenderError is raised
end

Complete gist with tests replicating this issue is available here: https://gist.github.com/kdough/c797c25088331ffc275bb2aa6159bde9

Expected behavior

Setting self.response_body = nil before a subsequent call to render should drop a previously-rendered response body.

Actual behavior

As of Rails 5+, a AbstractController::DoubleRenderError exception is raised when a second call to render is made. The previously-rendered content remains in the result of performed?

In Rails 4.2.6, the second call to render succeeds as expected.

System configuration

  • Working Setup:
    • Rails version: 4.2.6
    • Ruby version: 2.3.1p112
  • Broken Setup:
    • Rails version: 5.0.0.rc1
    • Ruby version: 2.3.1p112

About this issue

  • Original URL
  • State: open
  • Created 8 years ago
  • Comments: 15 (14 by maintainers)

Most upvoted comments

I just wanted to add another use case. We run into this problem when attempting to resolve a double render issue while rescuing from ActionController::InvalidCrossOriginRequest

  rescue_from ActionController::InvalidCrossOriginRequest, with: :raise_nothing

  def raise_nothing
    self.response_body = nil
    render status: 500, layout: false, file: 'public/500.html'
  end

Maybe this should be a separate issue related to the rescue_from method?

@matthewd @rafaelfranca Looks like the community needs a proper way of unsending a response, I’d like to make a PR introducing this functionality adding an ad-hoc method to the public API: sounds good to you?

From: /home/rth/Software/rails/actionpack/lib/action_controller/metal.rb @ line 177 ActionController::Metal#response_body=:

    174: def response_body=(body)
    175:   body = [body] unless body.nil? || body.respond_to?(:each)
    176:   response.reset_body!
 => 177:   return unless body #=> returns here when dev tries setting Response#response_body to nil
    178:   response.body = body
    179:   super
    180: end

My understanding from the conversation here and the work done on other (related?) issues is that it should be possible to reset the body as attempted by @kdough in normal requests and it should not be possible to reset the body in this way for streaming requests. /cc @matthewd & @rafaelfranca

I ran into the same issue while developing an OpenID Connect extension for the doorkeeper gem, the use-case is that certain behaviours are user-defined and might involve redirects, but in some cases I have to ignore that response and send my own. It would be nice to have a supported API for this, maybe a reset_body! method on the controller to mirror the one from the response.

Also, as a temporary workaround the following seems to work fine on 5.0.0.1 with non-streaming responses:

self.response_body = nil
@_response_body = nil