How to catch exceptions in controller's actions properly? - ruby-on-rails

There is the following code:
def index
#posts = User.find_by(login: params[:user_id]).posts
end
As you can see this code can generate exception if there is no user with some login (nil pointer exception). How can I catch this exception and handle it properly? I know how to catch exceptions in Ruby, but I want to know how to do in a good Rails style. The same problem may occur in different controllers - may be I should create an action wrapper, catch exception and render 500 error?

The easiest way is to use ApplicationController's rescue_from:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render 'my/custom/template', status: 404
end
end

def index
#posts = User.find_by!(login: params[:user_id]).posts
rescue ActiveRecord::RecordNotFound => error
# handle user not found case
end
You can also use rescue_from http://edgeapi.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html if you want to catch the error globally for the controller.

Related

raising NotAuthorized exception in application controller

I am trying to raise an exception from application_controller file for any actions other than read
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
private
def authenticate_request
#current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
def authorize!(action)
raise NotAuthorized if action != :read && !current_user.admin?
true
end
end
But when I am making a POST request, the exception itself is throwing error. The following is the error.
NameError (uninitialized constant ApplicationController::NotAuthorized):
How can I fix this without having to add any new modules?
In rails there is no in-built exception called NotAuthorized, so when you try to raise this, rails doesn't know what to do. You can do this in two ways:
You can raise directly using built-in exceptions or create your own. Ex:
raise ActionController::RoutingError.new('Not authorized')
# or
raise ActionController::BadRequest.new('Not authorized')
# or
raise ActionController::MethodNotAllowed.new('Not authorized')
I recommend creating an external module and including it here, it keeps you code clean.

How can I re-raise a Ruby exception in a Rails rescue_from statement?

My Rails 4 app uses RocketPants for its JSON API and Pundit for authorization.
I have code in my /app/controllers/api/v1/base_controller.rb file to handle errors from Pundit. Whenever a user isn't authorized to update a resource, Pundit throws a NotAuthorizedError exception and I rescue it with my user_not_authorized method:
class API::V1::BaseController < RocketPants::Base
include Pundit
version 1
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def user_not_authorized
error! :forbidden
end
end
When I call the error! method that RocketPants provides from my exception handler, I expect to get a JSON response like this:
{
"error": "forbidden",
"error_description": "The requested action was forbidden."
}
Instead, however, calling error! just immediately blows up the request:
Completed 500 Internal Server Error in 143ms
RocketPants::Forbidden - RocketPants::Forbidden:
rocket_pants (1.13.1) lib/rocket_pants/controller/error_handling.rb:44:in `error!'
app/controllers/api/v1/base_controller.rb:61:in `user_not_authorized'
Full stack trace here.
Why isn't the error! method doing what it should when called from my Pundit exception handler?
If I put error! :forbidden in the middle of my controller action, it works as expected.
For context, the controller that inherits from base_controller.rb and calls Pundit's authorize method looks like this:
class API::V1::MealsController < API::V1::BaseController
before_filter :find_entity
def create
meal = #entity.meals.build(meal_params)
authorize(#entity, :update?)
if meal.save
expose meal, status: :created
else
expose meal.errors, status: 422
end
end
end
Apparently raising exceptions in a rescue_from is a bad idea and according to the Rails docs, exceptions raised in a handler are not bubbled up:
Exceptions raised inside exception handlers are not propagated up.
Docs: http://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html
Instead of re-raising RocketPants' exception, I'm simply creating and returning the JSON error message myself:
def user_not_authorized
# error! :forbidden
head 403
error = { error: 'Action not allowed.', error_description: 'Sorry, you are not allowed to perform this action.'}
expose error
end
This works!
UPDATE
I've since found an even cleaner solution: just map the Pundit exception to the RocketPants exception. This means that whenever a Pundit::NotAuthorizedError error is raised it'll be treated as a RocketPants::Forbidden error.
Got the entire solution down to a single line of code at the top of base_controller.rb:
map_error! Pundit::NotAuthorizedError, RocketPants::Forbidden
No handler required.

Rails - rescue_from exception handling a exception type differently based on what method the exception is raised from

Rails's :rescue_from takes in a specific exception type and a method as parameter as follow:
class ApplicationController < ActionController::Base
rescue_from User::NotAuthorized, with: :deny_access # self defined exception
rescue_from ActiveRecord::RecordInvalid, with: :show_errors
rescue_from 'MyAppError::Base' do |exception|
render xml: exception, status: 500
end
protected
def deny_access
...
end
def show_errors(exception)
exception.record.new_record? ? ...
end
end
but this implies that it will deal with the specified exception in the same way ALL ACROSS the controller.
What if I want to handle an exception type differently based on what method the exception is raised from, Example:
class MyController < ActionController::Base
def method_1
# Do Something
rescue MyCustomError => e
handle_exception_for_method_1(e)
end
def method_2
# Do Something
rescue MyCustomError => e
handle_exception_for_method2(e)
end
protected
def handle_exception_for_method_1(exception)
# Do Something
end
def handle_exception_for_method_2(exception)
# Do Something
end
end
I have the following questions:
Can this be done by using :rescue_from as well (with any sort of options to pass in)?
If not, is there any better solution of dealing with this kind of situations?
(Kind of off topic but) Is it a bad practice to handle the same type of error differently in different methods in general?
Rails provides access to the controller and action names through controller_name and action_name methods. You could use this to handle exceptions differently based on the what method the exception was raised.
Example:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordInvalid, with: :show_errors
protected
def show_errors
if action_name == "create"
...
elsif action_name == ...
...
end
end

Redirect to 404 when rails app crash

i am new in rails. I want to add redirection to "404 page" whenever my "app crashed" or "page not found" or in case of "exception".
If someone have good tutorials please share with me or provide some simple solution.
I read this Rails_admin redirect to 404 but it did not solved my problem.
The Rails guide has a chapter about exception handling. You can use rescue_from to run a custom method, when an exception is raised. The following example is from that guide and should be added to your application_controller:
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "404 Not Found", status: 404
end
You may want to change the exception type to whatever exceptions you want to catch. I guess that you can also catch from every exception by:
rescue_from StandardError, with: :record_not_found
Note: I would consider this a bad practise and would instead just design my 500 error page to look the same then my 404 page.
I have this method in my application_controller.rb
def not_found
raise ActionController::RoutingError.new('Not Found')
end
and then in any controller when I do a find:
#model = Model.find_by(id: params[:id]) or not_found
I'm using Rails 2 at the moment though so it might be slightly different for your version.
For 404 i.e. page not found, we can redirect page to custom route.
For this you need create new route,
match "*path", to: "handle_errors#page_not_found", via: :all
which will redirect to page_not_found action of handle_errors controller.
Obviously you need to create a new controller handle_errors
For any other exception occurred in app, you can handle it using some customer method in application controller as
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found_error_handler
rescue_from ActiveRecord::RecordNotUnique, :with => :default_error_handler
as so on ...
with defination of methods as,
def default_error_handler(e)
render 'handle_errors/page_404', :status => 404
end
def record_not_found_error_handler(e)
render 'handle_errors/record_not_found'
end

rescue_from ::AbstractController::ActionNotFound not working

I have the following code:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :render_exception
rescue_from ActiveRecord::RecordNotFound, with: :render_exception
rescue_from ActionController::UnknownController, with: :render_exception
rescue_from ::AbstractController::ActionNotFound, with: :render_exception
rescue_from ActiveRecord::ActiveRecordError, with: :render_exception
rescue_from NoMethodError, with: :render_exception
end
They all work flawless, except ::AbstractController::ActionNotFound
I've also tried
AbstractController::ActionNotFound
ActionController::UnknownAction
error:
AbstractController::ActionNotFound (The action 'show' could not be found for ProductsController):
This similar question suggests that you can no longer catch an ActionNotFound exception. Check the link for workarounds. This suggestion to use a Rack middleware to catch 404s looks the cleanest to me.
To rescue AbstractController::ActionNotFound in a controller, you can try something like this:
class UsersController < ApplicationController
private
def process(action, *args)
super
rescue AbstractController::ActionNotFound
respond_to do |format|
format.html { render :404, status: :not_found }
format.all { render nothing: true, status: :not_found }
end
end
public
# actions must not be private
end
This overrides the process method of AbstractController::Base that raises AbstractController::ActionNotFound (see source).
I think we should catch AbstractController::ActionNotFound in ApplicationController. I have tried following that does not appears to be working.
rescue_from ActionController::ActionNotFound, with: :action_not_found
I have found much cleaner way to handle this exception in ApplicationController. To handle the ActionNotFound Exception in your application, you have to override the action_missing method in your application controller.
def action_missing(m, *args, &block)
Rails.logger.error(m)
redirect_to not_found_path # update your application 404 path here
end
Solution Adapted from: coderwall handling exceptions in your rails application
Overriding process, as described by Grégoire in his answer, seems to work. However, the Rails code says to instead override process_action. That doesn't work, though, because process_action never gets called due to the check for action_name in process.
https://github.com/rails/rails/blob/v3.2.21/actionpack/lib/abstract_controller/base.rb#L115
https://github.com/rails/rails/blob/v3.2.21/actionpack/lib/abstract_controller/base.rb#L161

Resources