tech_human
tech_human

Reputation: 7110

AbstractController::DoubleRenderError Error and using redirect_to

I have an update method which is using save and delete methods having their own render calls. Below is the code:

Update Method:

def update_book
  self.delete_book
  params[:Name] = params[:NewName]
  self.save_book
end

Delete Method:

def delete_book
 # Do something
 rescue BookDoesNotExist => exception
    render status: 404
    return
 end
 head 200
end

Save Method:

def save_book
 # Do something
 rescue BookDoesNotExist => exception
    render status: 404
    return
 end
 rescue BookAlreadyExist => exception
    render status: 409
    return
 end
 head 200
end

When I run this with some input the system throws "AbstractController::DoubleRenderError" error along with some message "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return"

I understand it is because the is throwing exception in both delete and save method with the input I am passing. How do I handle this scenario?

Doing some research I learnt I need to use redirect. So I understand that when my delete method throws exception/renders I should return back and should not call the save method.

Here is what I tried with redirect_to:

Update Method:

def update_book
  redirect_to action: self.delete_book and return if status=404 end
  params[:Name] = params[:NewName]
  self.save_book
end

Am I doing it right or is there any other best way to handle redirect?

Upvotes: 1

Views: 1427

Answers (1)

Benjamin Bouchet
Benjamin Bouchet

Reputation: 13181

When your delete_book or save_book methods receive an exception they will render something and then return, this is correct.

But the return will give execution back to the caller method, which is update_book. This caller method have no idea of the exception that occurred and therefore will do its job and render what it has to render.

To fix, your delete_book or save_book methods need to return a success status to the caller, like this:

def delete_book
  # ... Do something

  rescue BookDoesNotExist => exception
    render status: 404
    return false
  end

  head 200
  true
end

Then your caller method shall check for this status:

def update_book
  return unless self.delete_book
  params[:Name] = params[:NewName]
  return unless self.save_book
end

An alternative that will keep your code clear from those heavy exception testing would be to use your controller'srescue_from.

In your ApplicationController:

class ApplicationController < ActionController::Base
  rescue_from BookDoesNotExist, with: :redirect_book_not_existing

  private 

  def redirect_book_not_existing
    render status: 404
  end
end

From now on, very time your controllers receive a BookDoesNotExist exception, the execution will stop and the render status: 404 line will be executed. Off course you must not rescue the exceptions in the other method, like this:

def delete_book
 # Do something

 head 200
end

Your code will be much cleaner

Upvotes: 3

Related Questions