I would like to send e-mail when having an exception in my application and render the regular 500 page. I could not find how to perform the 500 page render:
class ApplicationController < ActionController::Base
rescue_from StandardError do
send_email_of_error
# what goes here?
end
...
end
Raising the exception again will likely to what you want:
rescue_from StandardError do |exception|
send_email_of_error
raise exception
end
You could also call render to render your own page, the docs have an example doing this.
But why reinvent the wheel? The exception notifier gem already does this and is customizable and tested.
This is an approach that maybe fits your needs:
class ApplicationController < ActionController::Base
rescue_from Exception, :with => :render_500
def render_500(exception)
#exception = exception
render :template => "shared/500.html", :status => 500
end
end
Related
I was trying to handle routing error when I'm loading images and some are missing.
You know I wanted just to replace a missing image with the default image icon and to suppress error message.
So I tried
class ImagesController < ApplicationController
[...]
def index
images = Image.all
rescue_from ActionController::RoutingError, with: :image_route_error
end
[...]
end
Then I got this:
NoMethodError (undefined method `rescue_from' for #<ImagesController:0x007fe382227e38>
Did you mean? rescue_handlers):
Any ideas?
You can rescue_from any kind of exceptions other than server errors using rescue_from method. You write this method in your ApplicationController.
rescue_from ActionController::RoutingError do |exception|
if controller_name == "image" && action_name == "index"
render 'default_image_here', status: 200
else
render plain: 'Not found', status: 400
end
end
In render 'default_image_here' you can use this:
render :text => open(image_url, "rb").read, status: 200
This will read file as binary instead of text.
In this post, the errors are rescued in the both api and base controller methods. But it might not be best approach to handle errors because of some reasons are:
Fat Controllers
DRY
Maintainability
In ActionController::Base, we handled ActiveRecord::RecordNotFound in only ApplicationController. But for ActionController::API i have to rescue ActiveRecord::RecordNotFound in every controller. So are there any best approach for handle this problem?
Using Rails 5 and 'active_model_serializers' gem for api
ActionController::API
module Api
module V1
class UsersController < ActionController::API
before_action :find_user, only: :show
def find_user
#user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.to_s }, status: :not_found
end
end
end
end
ActionController::Base
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render file: "#{Rails.root}/public/404", layout: true, status: :not_found
end
end
You can do something like this in application_controller.rb
if Rails.env.production?
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
def render_404
render json: {meta: meta_response(404, "Record not found")}
end
This would rescue all RecordNotFound exception with 404 but only in production mode.
ActionController::API includes the ActionController::Rescue module which is what provides the rescue_from class method.
I would create an Api::BaseController base class that the Api::V1::UsersController can use instead of using ActionController::API on each controller class. This would allow you have a rescue_from in a single place instead of needing a rescue block on every action.
module Api
class BaseController < ActionController::API
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
private
def handle_error(e)
render json: { error: e.to_s }, status: :bad_request
end
end
module V1
class UsersController < BaseController
def find_user
#user = User.find(params[:id])
end
end
end
end
I'd also further create an Api::V1::BaseController to allow for easier versioning of the APIs. Then, if you decide to change the format of the errors for v2, just move the rescue_from in the Api::BaseController to the Api::V1::BaseController, and add a new rescue_from to the new Api::V2::BaseController.
module Api
class CommonBaseController < ActionController::API
# code common across API versions
end
module V1
class BaseController < CommonBaseController
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
private
def handle_error(e)
render json: { error: e.to_s }, status: :bad_request
end
end
end
module V2
class BaseController < CommonBaseController
# use a custom base error class to make catching errors easier and more standardized
rescue_from BaseError, with: :handle_error
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
private
def handle_error(e)
status, status_code, code, title, detail =
if e.is_a?(ActiveRecord::RecordNotFound)
[:not_found, '404', '104', 'record not found', 'record not found']
else
[
e.respond_to?(:status) ? e.status : :bad_request,
e.respond_to?(:status_code) ? e.status_code.to_s : '400',
e.respond_to?(:code) ? e.code.to_s : '100',
e.respond_to?(:title) ? e.title : e.to_s,
e.respond_to?(:detail) ? e.detail : e.to_s
]
end
render(
json: {
status: status_code,
code: code,
title: title,
detail: detail
},
status: status
)
end
end
end
end
I have a homepage and a controller called StaticPagescontroller.
I used a rescue (as you see below) but an error on the page showed me it was a bad idea, or at least I was doing it the wrong way.
Any problem on the homepage triggers the rescue that points to the root_page which is the homepage, giving me an infinite loop that was blocking everything, which was a bad idea!
What's the best practice?
What should I do with rescue on the homepage?
Should I point it to a 404 page? To something else?
class StaticPagesController < ApplicationController
def home
#deals = Deal.featured.to_a
rescue
redirect_to authenticated_root_path
end
When an error happens I like to fist log the error and then redirect to a 404 page.
This is how I do it.
class StaticPagesController < ApplicationController
def home
#deals = Deal.featured.to_a
rescue => error
handle_error(error)
end
end
now my ApplicationController would look something like this
class ApplicationController < ActionController::Base
def handle_error(error)
Error.create(error)
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
end
end
a good thing to know is that the files in the public directory render before rails server starts so they can still render.
I hope that this helps
I'm using Rails 3.2.3 with ActiveResource.
I have an issue in production that says:
ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found.
So I tried to treat it the same way I treat ActiveRecord::RecordNotFound:
class ApplicationController < ActionController::Base
protect_from_forgery
rescue_from ActiveRecord::RecordNotFound do |e|
render_404
end
rescue_from ActiveResource::ResourceNotFound do |e|
render_404
end
def render_404
respond_to do |type|
type.html { render template: 'shared/404_not_found', layout: 'application', status: '404 Not Found' }
type.all { render nothing: true, status: '404 Not Found' }
end
end
end
But now, when I deploy, I get an error telling me that:
/apps/com.example/shared/bundle/ruby/1.9.1/gems/activeadmin-0.4.3/lib/active_admin/namespace.rb:191:in `eval': uninitialized constant ActiveResource::ResourceNotFound (NameError)
I don't really get it. I tried with a if defined?(ActiveResource::ResourceNotFound) but then it falls back to the previous behavior.
Any idea of how to treat this issue ?
Thanks !
EDIT: For the moment I used the following code but I'm not really happy with it.
rescue_from Exception do |e|
e.is_a?(ActiveResource::ResourceNotFound) ? render_404 : raise
end
Heh, ignore my comment I figured out a solution:
rescue_from "ActiveResource::ResourceNotFound" do |e|
render_404
end
Put the exception in quotes so it doesn't try to evaluate it on startup (when, I assume, ActiveResource hasn't loaded yet)
Is there a way to globally define a rails app to only serve json and xml and appropriately error on any other requests?
I'm thinking it's something along the lines of a before_filter and responds_to block in the ApplicationController but that's as far as my investigation has got me.
Just declare it at the class level on your controller, using respond_to. It will apply to all your controllers if you do it on ApplicationController
class ApplicationController < ActionController::Base
respond_to :xml, :json
…
end
Also read about ActionController::Responder class for more options.
To make json response on errors, just add the following code to your application_controller:
rescue_from Exception, :with => :render_error
rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
rescue_from ActionController::RoutingError, :with => :render_not_found
rescue_from ActionController::UnknownController, :with => :render_not_found
rescue_from ActionController::UnknownAction, :with => :render_not_found
private
def render_not_found(exception)
# logger.info(exception) # for logging
respond_to do |format|
render json: {:error => "404"}, status: 404
end
end
def render_error(exception)
# logger.info(exception) # for logging
respond_to do |format|
render json: {:error => "500"}, status: 500
end
end
public
def some_public_func
#do sthg
end