raising NotAuthorized exception in application controller - ruby-on-rails

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.

Related

How to deal with general errors in Rails API?

Trying to set up rails API, getting this error, both through console and in actual API requests:
Rack app error handling request { POST /login } #<NameError: uninitialized constant ActionText::Engine::ApplicationController
This is my ApplicationController:
class ApplicationController < ActionController::API
before_action :authorized
def encode_token(payload)
JWT.encode(payload, 's3cr3t')
end
def auth_header
#requesting the header type of authorization (with token) that we will declare through our api requests
# { Authorization: 'Bearer <token>' }
request.headers['Authorization']
end
def decoded_token
if auth_header
#going to take the token and decode it
# we're only concerned about the first index which will be a token with key of *user_id*
token = auth_header.split(' ')[1]
# header: { 'Authorization': 'Bearer <token>' }
begin
JWT.decode('s3cr3t', true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
def logged_in_user
#consults decode_token to check the header for valid information
if decoded_token
user_id = decoded_token[0]['user_id']
#user = User.find_by(id: user_id)
end
end
def logged_in?
#returns true or false
!!logged_in_user
end
def authorized
#consults logged_in? see see if user is authorized
render json: { message: 'Please log in' }, status: :unauthorized unless logged_in?
end
end
Of course I would like to sort this error specifically (syntax error?) but not sure how to tackle general errors (beyond status codes) in Rails API. Is there a good practice I should be following?
Thanks!
For NameError: uninitialized constant ActionText::Engine::ApplicationController, where is your ApplicationController defined? It seems like ActionText requires it to be in app/controllers/application_controller.rb and possibly inherited from ActionController::Base.
As you develop an API I don't expect you need ActionText though and just accidentally load it. You should have a look in your config/application.rb file and look what gets loaded. If there is require 'rails/all' you should only load what you really need, e.g.
require "action_controller/railtie"
require "active_record/railtie"
but not sure how to tackle general errors (beyond status codes) in Rails API
In terms of general errors, you can e.g. use a rescue_from like this
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
private
def deny_access
...
end
def show_errors(exception)
exception.record.new_record? ? ...
end
end
https://apidock.com/rails/ActiveSupport/Rescuable/ClassMethods/rescue_from
For general errors it's not really needed to have a rescue_from StandardError as this is the default behaviour of Rails. Rails has a middleware called PublicExceptions which does (mostly) what you want so you can just let the StandardError propagate.
Instead of { error: "Internal Server Error" } it will render this
{
status: status,
error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500])
}
which in case of an exception will render { status: 500, error: "Internal Server Error" }. This should be a reasonable compromise.
For development you could think about adapting this middleware. You can set it with config.exceptions_app.
https://guides.rubyonrails.org/configuring.html#rails-general-configuration
https://api.rubyonrails.org/classes/ActionDispatch/PublicExceptions.html
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/public_exceptions.rb#L14

Why is this raise not rescued, and what will correct it so that it is?

I am trying to create my own StandardError exception, but I cannot seem to fire rescue_from with the raise. The error is raised but never rescued. I built a simple application to try it, as follows:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
class ApplicationController::LocationInvalid < StandardError
end
rescue_from ApplicationController::LocationInvalid, with: :reset_it
raise ApplicationController::LocationInvalid
private
def reset_it(exception)
session.clear
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
puts 'Reset it processed'
end
end
When I wrap the raise in begin/rescue/end, it prints e as ApplicationController::LocationInvalid:
begin
raise ApplicationController::LocationInvalid
rescue => e
puts e
end
Output:
Started GET "/" for 127.0.0.1 at 2015-08-21 15:19:32 -0400
ApplicationController::LocationInvalid
I've tried various forms of specifying the ApplicationController class and not with no change in the results...
According to source code of Rails https://github.com/rails/rails/blob/f62fb985b6a7b90872148f0786219c9cc94c9356/actionpack/lib/action_controller/metal/rescue.rb#L32
rescue_with works only during process_action call but you are raising exception during class evaluation. Probably the easiest way how to test it is to create simple controller with some action and try to raise exception during that action and rescue_with should work.

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

How to catch exceptions in controller's actions properly?

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.

Can I execute a method everytime I get an exception on Rails 3?

I tried almost everything on web, all I want is to call a method whenever an exception like "ActiveRecord::RecordNotFound" or "No route matches" appears.
Rescues from ApplicationController does not work, but why?
class ApplicationController < ActionController::Base
protect_from_forgery
private
def self.send_report_error(message)
Notifier.page_failure(message).deliver
end
rescue ActiveRecord::RecordNotFound
# handle not found error
send_report_error ActiveRecord::RecordNotFound.to_s
rescue ActiveRecord::ActiveRecordError
# handle other ActiveRecord errors
send_report_error ActiveRecord::ActiveRecordError.to_s
rescue # StandardError
# handle most other errors
send_report_error "common error"
rescue Exception
# handle everything else
send_report_error "common exception"
end
Use rescue_from. For example:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :send_report_error
end
http://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html

Resources