So I have a fairly common rescue_from block in a Rails app:
if Rails.env.production?
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: lambda { |exception| render_error 500, exception }
rescue_from Mongoid::Errors::DocumentNotFound, with: lambda { |exception| render_error 404, exception }
end
end
but I want to be able to see the error message if I'm an admin user, so I change the "unless" line to:
unless Rails.application.config.consider_all_requests_local || (current_user.present? && current_user.site_amdin)
but rails complains: "undefined local variable or method `current_user' for ApplicationController:Class"
So how can I access the instance variables, since the code isn't within a block?
I also tried to wrap it in the before_filter block:
before_filter do
if Rails.env.production? || (current_user.present? && current_user.site_admin)
unless Rails.application.config.consider_all_requests_local
Application.rescue_from Exception, with: lambda { |exception| render_error 500, exception }
Application.rescue_from Mongoid::Errors::DocumentNotFound, with: lambda { |exception| render_error 404, exception }
end
end
end
but the app wouldn't run on the server.
"rescue_from" is class-level method and doesn't have access to the instance variables. However, you can access them from a method that is called in with:
if Rails.env.production?
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :show_exception
rescue_from Mongoid::Errors::DocumentNotFound, with: lambda { |exception| render_error 404, exception }
end
end
# at the end of file
protected
def show_exception(exception)
if current_user.present? && current_user.site_admin
render text: ([exception.message] + exception.backtrace).join('<br />') # render error for admin
else
render_error 500, exception
end
end
If you haven't found a solution yet, you can try this trick:
unless Rails.application.config.consider_all_requests_local || (Thread.current[:user].present? && Thread.current[:user].site_amdin)
I agree this approach has some minuses but it worths trying when other possibilities are exhausted.
Related
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.
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
I'd like to rescue from a RecordNotFound exception if, and only if, the request is JSON. Now if I was doing this for a skip_before_action, I would do the below:
skip_before_action :verify_authenticity_token, if: :json_request?
Is there syntax for this in rescue_from? Something like:
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found, if: :json_request?
Helper method:
protected
def json_request?
request.format.json?
end
I am assuming if the request is not JSON then you want to raise? If so you should be able to do this
rescue_from ActiveRecord::RecordNotFound { request.format.json? ? record_not_found : super }
OR
rescue_from ActiveRecord::RecordNotFound, with: lambda{|e| request.format.json? ? record_not_found(e) : raise(e) }
These will have identical impact because if a block is given it assigns it to the options[:with] where as if with: is supplied it uses this as the block and ignores any other block passed to it
rescue_from takes a splat argument called *klasses and a block. It then parses *klasses to determine the options passed in of which it only cares about :with. It then applies the :with block to the key(s) which will represent exception class names to handle.
There is no additional options that will be acknowledged.
Please be advised I have not tested this
You could do:
rescue_from ActiveRecord::RecordNotFound do
record_not_found if json_request?
end
I dont think there is another syntax for that exact case :-(
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
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