I'm using Doorkeeper with rails-api to implement oAuth2 with password workflow:
resource_owner_from_credentials do
FacebookAuthorization.create(params[:username])
end
Right now when exception occurs it displays 500 template html response from rails. What I want to do is rescue any unexpected exception, then I want to custom the response error message base on exception that occurred in json response.
as the classes defined within the doorkeeper API will extend the Application controller, we can define the following in the Application controller
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :render_500
rescue_from ActionController::RoutingError, with: :render_404
rescue_from ActionController::UnknownController, with: :render_404
rescue_from ActionController::UnknownAction, with: :render_404
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
private
#error handling
def render_404(exception)
#not_found_path = exception.message
respond_to do |format|
format.html { render file: 'public/404.html', status: 404, layout: false }
format.all { render nothing: true, status: 404 }
end
end
def render_500(exception)
#error = exception
respond_to do |format|
format.html { render file: 'public/500.html', status: 500, layout: false }
format.all { render nothing: true, status: 500}
end
end
Then you can define the errors specifically in the ErrorsController
class ErrorsController < ActionController::Base
def not_found
if request.url.match('api')
render :json => {:error => "Not Found"}.to_json, :status => 404
else
render file: 'public/404.html', :status => 404, layout: false
end
end
def exception
if request.url.match('api')
render :json => {:error => "Internal Server Error"}.to_json, :status => 500
else
render file: 'public/500.html', :status => 500, layout: false
end
end
end
Hope this helps.
Related
Currently i am catching the error not_found like this
def show
begin
#task = Task.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.to_s }, status: :not_found and return
end
and the rspec test would be like this expect(response).to be_not_found
but i dont want to do that (rescue ActiveRecord::RecordNotFound => e) in every single function (update, create, destroy and so on)
there is another way?
for example this way
rescue_from ActiveRecord::RecordNotFound, with: :not_found
def not_found
respond_to do |format|
format.json { head :not_found }
end
end
but i dont know how can i test with that
i would like to test the same way
expect(response).to be_not_found
I think that your original implementation with an error node is a better response but your modified change is a better way to handle so I would suggest combining the 2 concepts via
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def not_found(exception)
respond_to do |format|
format.json do
# I expanded the response a bit but handle as you see fit
render json: {
error: {
message: 'Record Not Found',
data: {
record_type: exception.model,
id: exception.id
}
}
}, status: :not_found
end
end
end
You should be able to maintain your current test in this case while avoiding the need to individually handle in each request.
You can add the below code in your application_controller.rb.
around_filter :catch_not_found #=> only:[:show, :edit]
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json { render json: { error: e.to_s }, status: :not_found and return }
format.html { redirect_to root_url, :flash => { :error => "Record not found." } and return }
end
end
Below is the simple example for test cases using RSpec. Modify as per your requirements.
staff_controller.rb
def show
#staff = Staff.find(params[:id])
end
RSpec
let(:staff) { FactoryBot.create(:staff) }
describe "GET #show" do
it "Renders show page for valid staff" do
get :show, {:id => staff.to_param}
expect(response).to render_template :show
end
it "redirects to root path on staff record not_found" do
get :show, id: 100
expect(response).to redirect_to(root_path)
end
end
In application_controller.rb there is a method to render 404 errors with a custom layout and partial.
application_controller.rb:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :render_500
rescue_from ActionController::RoutingError, with: :render_404
rescue_from ActionController::UnknownController, with: :render_404
rescue_from ActionController::UnknownAction, with: :render_404
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
This calls any of the methods:
def render_404(exception)
notify exception
#not_found_path = exception.message
respond_to do |format|
format.html { render template: 'errors/error_404', layout: 'layouts/error', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500(exception)
notify exception
#error = exception
respond_to do |format|
format.html { render template: 'errors/error_404', layout: 'layouts/error', status: 404 }
format.all { render nothing: true, status: 500 }
end
end
Then there is error_controller.rb:
class ErrorsController < ApplicationController
layout "error"
def sub_layout
"left"
end
def error_404
#not_found_path = params[:not_found]
render "errors/error_404"
end
def error_500
render "errors/error_500"
end
end
QUESTION:
This feels very bloated, Is there a way to have it render the custom error page now situated in /app/views/errors/error_404.html.haml ? with custom layout?
I want to delete my error_controller.rb
The default path for rendering 404 and 500 html responses is in the public folder. I'd replace the default 404.html with your error_404.html
render :file => 'public/404.html', :layout => layouts/error
We have a rails server with custom 404 and 500 pages setup using this tutorial here:
http://ramblinglabs.com/blog/2012/01/rails-3-1-adding-custom-404-and-500-error-pages
While it works nice and throws 404s for all kinds of paths, it generates internal server errors 500 while trying to access any kind of suffixed path like en/foo.png, en/foo.pdf, en/foo.xml, ...
But something like en/file.foo throws 404. So only valid suffixes throw a 500.
End of routes.rb:
if Rails.application.config.consider_all_requests_local
match '*not_found', to: 'errors#error_404'
end
application_controller.rb
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :render_500
rescue_from ActionController::RoutingError, with: :render_404
rescue_from ActionController::UnknownController, with: :render_404
rescue_from ::AbstractController::ActionNotFound, with: :render_404
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
protected
def render_404(exception)
#not_found_path = exception.message
respond_to do |format|
format.html { render template: 'errors/error_404', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500(exception)
logger.fatal(exception)
respond_to do |format|
format.html { render template: 'errors/error_500', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
500 that appears:
Missing template errors/error_404 with {:locale=>[:de, :en], :formats=>[:png], :handlers=>[:erb, :builder, :coffee, :arb, :haml]}
We found the mistake.
We had an error_controller.rb containing this:
def error_404
#not_found_path = params[:not_found]
render template: 'errors/error_404', layout: 'layouts/application', status: 404
end
and we changed it to fix this problem to:
def error_404
#not_found_path = params[:not_found]
respond_to do |format|
format.html { render template: 'errors/error_404', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
Try adding
respond_to :html, :json, :png
and any other necessary formats at the top of your controller. If I'm right, then the problem is that format.all in the individual controller actions isn't set up to include :png as one of the formats it responds to.
You will probably also need to add to your config/environment.rb the following definition and any similar ones:
Mime::Type.register "image/png", :png
See more details here. Basically you need to set up the mime types that you want to respond to. The error message indicates that rails doesn't understand how to render the format png.
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
When going to on object's show page with an id that doesn't exist, the RecordNotFonud exception is thown. Is there a way I can redirect to some error page, or maybe a different action when this error is thrown?
You may use rescue_from if you are using Rails 3:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
def render_404
respond_to do |format|
format.html { render :action => "errors/404.html.erb", :status => 404 }
# and so on..
end
end
end
Yes, you can also do a redirect instead of render, but this is not a good idea. Any semi-automatic interaction with your site will think that the transfer was successfull (because the returned code was not 404), but the received resource was not the one your client wanted.
In development mode you'll see the exception details but it should automatically render the 404.html file from your public directory when your app is running in production mode.
See http://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html. Rails has nice features for exception handling.
I generally do something like this in my ApplicationController
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :routing_error
private
def routing_error
redirect_to(root_url, :alert => "Sorry, the page you requested could not be found.")
end
end
If you need to handle more than one specific exception, use rescue_action or rescue_action_in_public, the difference in to hook local requests or not (development/production in common). I prefer to use in_public , because need to review exception's backtrace in development mode.
take a look at my source code:
class ApplicationController < ActionController::Base
include CustomExceptionsHandler
....
end
module CustomExceptionsHandler
# Redirect to login/dashboard path when Exception is caught
def rescue_action_in_public(exception)
logger.error("\n !!! Exception !!! \n #{exception.message} \n")
case exception.class.to_s
when "Task::AccessDenied"
logger.error(" !!! 403 !!!")
notify_hoptoad(exception) //catch this kind of notification to Hoptoad
render_403
when "AuthenticatedSystem::PermissionDenied"
logger.error(" !!! 403 !!!")
render_403
when "Task::MissingDenied"
logger.error(" !!! 404 !!!")
notify_hoptoad(exception)
render_404
when "ActionController::RoutingError"
logger.error(" !!! 404 !!!")
render_404
else
notify_hoptoad(exception)
redirect_to(current_user.nil? ? login_path : dashboard_path) and return false
end
end
private
#403 Forbidden
def render_403
respond_to do |format|
format.html { render :template => "common/403", :layout => false, :status => 403 }
format.xml { head 403 }
format.js { head 403 }
format.json { head 403 }
end
return false
end
#404 Not Found
def render_404
respond_to do |format|
format.html { render :template => "common/404", :layout => false, :status => 404 }
format.xml { head 404 }
format.js { head 404 }
format.json { head 404 }
end
return false
end
end
Use a begin - rescue - end construct to catch the exception and do something useful with it.
userid=2
begin
u=User.find userid
rescue RecordNotFound
redirect_to "/errorpage" #Go to erropage if you didn't find the record
exit
end
redirect_to u # Go to the user page