Rails 7 API only doesn't rescue from ActionController::RoutingError [duplicate] - ruby-on-rails

This question already has answers here:
rescue_from ActionController::RoutingError doesn't work
(2 answers)
Closed last month.
In my Rails 7 API only mode, I'm trying to catch case when someone is trying to make a request into api/v1/mandate instead of api/v1/mandates (classic ActionController::RoutingError case). In such case it will render the 404 error with some build in stuff as JSON response. I want to customize that JSON error answer inside my BaseController. I was trying:
#base_controller.rb
module Api
module V1
class BaseController < ActionController::API
rescue_from ActionController::RoutingError, with: :render_not_found_error
private
def render_not_found_error(exception)
render json: { error: true, message: exception.message }, status: :not_found
end
end
end
end
I've also have:
#config/environments/development.rb
config.consider_all_requests_local = true
But nothing changed, still doesn't get my JSON error.

You can't rescue routing errors in a controller because the exceptions occur in the routing layer, not the controller.
For missing routes, you need a "catch-all" route that can capture them and then forward that to a controller which performs your "exception" handling.
In practice this often shakes out as a NotFoundController with different actions depending on the type of route being handled.

Related

Rails default error page doesn't show on development

I built an error handler that, when there's an error in any controller method in production, reroutes the user to an error page and sends me, the developer, a notification email. This works, but I want the normal error screen to appear when I'm in development. I assumed the code to produce this was just raise e, but instead in development I'm now getting the default production error page (the one that says "We're sorry, but something went wrong".), instead of the detailed error message and trace that used to appear.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_error
#error handler
def handle_error(e)
if Rails.env.production?
#code to send email and redirect to error page
else
raise e
end
end
end
I also tried the following:
raise
raise StandardError.new e
raise e, e.message
raise e.message
and if I run any of those in a binding.pry console, they produce the error message I'm looking for, but the error page still just says "We're sorry, but something went wrong."
Anyone know how I can just show the default development error page?
UPDATE
This is insane...so the code to display an error normally should definitely work, but something somewhere is preventing that. If I change config.consider_all_requests_local = true on production, the errors show up on production, but then even if I copy and paste my config/environments/production.rb file into my config/environments/development.rb, the errors still don't show on development. If I enter a pry console, request.local? returns "0", signifying true, and Rails.env returns "development". I have no idea what is going on.
UPDATE 2
Apparently I'm not supposed to be rescuing exceptions on development, but even if I delete every bit of custom error handling code so my Application Controller is just empty, my errors still don't show on development. Further, I have a different app with the same exact error handling code, and for that the errors do show.
Search your code for consider_all_requests_local, it's this configuration that show the full error log.
It must be set as true on your development.rb config file. It's either missing from your configs, or other config is overwriting it
This is not "insane", this is completely expected behavior. You cannot raise from within a rescue_from handler. That would cause an infinite loop.
You also cannot rescue_from StandardError as stated specifically in the documentation:
Using rescue_from with Exception or StandardError would cause serious side-effects as it prevents Rails from handling exceptions properly. As such, it is not recommended to do so unless there is a strong reason.
Instead of conditionally handling the exception inside your rescue_fromhandler, you should conditionally bind the handler, and pick a more specific exception class to handle.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_error if Rails.env.production?
#error handler
def handle_error(e)
#code to send email and redirect to error page
end
end
create ErrorsController like below
class ErrorsController < ApplicationController
skip_before_action :login
def not_found
respond_to do |format|
format.html { render status: 404 }
format.json { render json: { error: "Not found" }, status: 404 }
end
end
end

Rails: Test against and handle ActionController::InvalidAuthenticityToken

I have a Rails web app with some forms users can use to comment etc. They are frequently being used by spammers trying to create spam comments. Nothing new about that.
Sometimes I get CSRF-hack attempts which causes an ActionController::InvalidAuthenticityToken exception. This happens quite a lot and I would like to rescue and send the user/bot to a "You failed to comment"-page.
This has turned out to be a tricky exception to catch though since I can't recreate the error myself. I first put a rescue in the #create when the model (created by the form) was to be saved but it doesn't catch the exception. In order to do so I consider having it cover the entire controller section but that seems over the top.
My two questions:
Is there a way to re-create an ActionController::InvalidAuthenticityToken error by myself, so I can test it?
When is the exception raised? During .save? On Comment.new? Basically, where should I put my begin/rescue?
Thanks!
You can rescue from this exception in your controller. One way you can set this up is to add a rescue in your ApplicationController.
class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_auth_token
private
def record_not_found
render :text => "You failed to comment", :status => 422
end
end
You could also just capture the exception locally in your controller action.
def create
begin
# Create your comment
rescue ActionController::InvalidAuthenticityToken
# Render your last view with some error text.
end
end
You can add raise ActionController::InvalidAuthenticityToken inside your action if you want to test it out.

rocketpant how to handle 404 routing error

I am using rocketpant for rest API implementation. From this doc I can see rocketpant has considered error handling in controller nicely.
However, it seems like it does not consider routing error, so if the requested url has no route defined, instead of returning json error, it render back html 404 page, which is not correct for API design.
Do I miss something? Or is there a way to wrap 404 routing error and render a json error message?
I found the solution.
Step1: catch all unmatched routes in the last line of routing file.
match '*a', to: 'error#routing', via: [:get, :post, :put, :patch]
Step2: define a error controller and routing method as custom error handler. throw rocketpant built in :not_found error
module Api
module V1
class ErrorController < ApiController
def routing
error! :not_found # throw not found error, let rocketpant handle error message rendering
end
end
end
end

DRYing up Rails error page

I have a Rails app which is part of a larger web site. I'd like to have a single 404 error page that's used consistently across the site. Currently I have a static page that's part of the landing site, which for historical reasons is served from the public section of the Rails app:
public/landing/404.html
Now I'd like my Rails app to serve that page in case of a 404 error. The approach I've tried is adapted from this blog post:
config/application.rb:
config.exceptions_app = self.routes
config/routes.rb:
match '/404', to: redirect('/landing/404'), via: :all
This appears to work, in that a 404 error will deliver the landing/404.html page to the user agent that made the request. However, it delivers the page with status 200, because the server successfully redirected to the static page. So, not web standards compliant (and not very RESTful!).
My question is: can I serve the static page but with a 404 response code? Or is there a better way to DRY up my error page config?
You can do the following in application_controller.rb:
unless Rails.application.config.consider_all_requests_local
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
Here, as you can guess, you can catch any type of exceptions you are interested in (ActionController::RoutingError, ActionController::UnknownController, or generally Exception), and it will only redirect in production, whereas in development it will show real exception.
def render_404
respond_to do |format|
format.html { render( layout: false, file: Rails.root.join( 'public', 'landing', status.to_s ), status: status ) }
format.all { render nothing: true, status: 404 }
end
end
and leave routes.rb as it is.

Catch Unknown action in Rails 3 for custom 404

I want to catch unknown action error in Rails 3, that shows "Unknown Action" error on development and the 404.html on production. I tried putting this rescue_from handler on my ApplicationController (and also on an actual controller, just in case) but I still see the ugly error.
I have custom stuff on the 404, and it can't be plain .html file.
My route:
match '/user/:id/:action', controller: 'users'
The URL I'm accessing: /user/elado/xxx
The rescue_from code:
rescue_from AbstractController::ActionNotFound, :with => :action_not_found
def action_not_found
render text: "action_not_found"
end
The error in the browser:
Unknown action
The action 'xxx' could not be found for UsersController
And in the console:
Started GET "/user/elado/xxx" for 127.0.0.1 at 2011-09-07 19:16:27 -0700
AbstractController::ActionNotFound (The action 'xxx' could not be found for UsersController):
Tried also rescue_from ActionController::UnknownAction.
Any suggestions?
Thanks!
rescue_from was slightly broken when Rails 3 came out (still broken in 3.1 too). Basically you can't:
rescue_from ActionController::RoutingError
anymore. See here.
The solution, for now, is what hamiltop recommends. Use a catch all route that goes to your "routing error" route. Make sure you put it at the end of your config\routes.rb file so it is processed last.
# Any routes that aren't defined above here go to the 404
match "*a", :to => "application#routing_error"
def routing_error
render "404", :status => 404
end
Note: This method has one major drawback. If you use an engine such as Jammit or Devise the catch all will route will make Rails ignore the engine's routes.
If you aren't using an engine that has it's own routes then you should be fine.
However, if you do use an engine that defines its own routes see #arikfr's answer.
Using a catch all route to handle 404 erros (as #Seth Jackson suggested) has one major drawback: if you use any Rails engines that define their own routes (such as Jammit) their routes will be ignored.
Better and more compliant solution would be to use a Rack middleware that will catch 404 errors. In one of my projects, I've implemented such Rack middleware that reports these errors to Hoptoad. I've based my implementation on this one: https://github.com/vidibus/vidibus-routing_error, but instead of invoking my Rails app again to handle the 404 error, I do it in the Rack middleware and let nginx to show the 404 page.
If you really want 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).
Have you tried a catch all route?
http://railscasts.com/episodes/46-catch-all-route
The wildcard route that you are current using is a Bad Idea(tm).
I would recommend defining the routes you care about, and then doing a catchall as the last line of routes.rb (routes defined first in routes.rb trump later definitions). Then you can render whatever page you want (and specify the 404 status code).
Edit: If you really want to use your current approach... (although this seems like it could be deprecated)
def rescue_action(exception)
case exception
when ActionNotFound, UnknownAction then
# Handle these exceptions here
else
super
end
end

Resources