I'm often guilty of producing this error from my Rails controllers:
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action.
I know the gist of why it occurs, but there are a few wrinkles that seem to trip me up. Time to iron those out.
The basic notion is that we cannot render and/or redirect more than once in a controller action. Okay, that makes sense. It is easy to assume that a redirect_to
statement would end the execution of an action, but it doesn't. The easiest case to fix is when there are multiple redirect_to or renders just within the action itself
def show
if params[:type] == 'home'
redirect_to home_path
end
redirect_to user_path
end
The code above will produce the DoubleRenderError
if params[:type] == 'home'
. We can fix this by adding an explicit return after the redirect: redirect_to home_path and return
, or by wrapping the rest of the action in an else
clause. Either way will work.
The next more devious case is from within a method called from the action.
def show
redirect_if_home(params[:type])
redirect_to user_path
end
private
def redirect_if_home(type)
if type == 'home'
redirect_to home_path and return
end
end
This seems to be a nice refactoring, but now the and return
statement just returns from the redirect_if_home
method, and we still get the DoubleRenderError
.
The final example is with before_action
calls. If you've been bitten by the above example, and are paying attention to redirect_to
within a private method, it's easy to fall into this final case:
before_action :redirect_if_home
def show
redirect_to user_path
end
private
def redirect_if_home
if params[:type]
redirect_to home_path and return
end
end
This works, but the and return
is unnecessary. Why, what's the difference with and without the before_action
. It turns out that before_action
will cancel the execution of the action if a redirect_to
or render
is called within the before_action
.
Here is the documentation that describes that behavior https://guides.rubyonrails.org/action_controller_overview.html#filters
If a "before" filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter, they are also cancelled.
Keep these three principles in your head when thinking about or fixing a DoubleRenderError:
and return
statement from within a called method does not stop execution in the calling method (the action).redirect_to
/render
inside of a before_action
prevents the action from being called.