I'm trying to rescue from ActionController::RoutingError and I can't get it to work. I tried almost everything that I could find online, including rescue_from ActionController::RoutingError in Rails 4. I have an errors controller and error pages. I got to work cancan access denied and RecordNotFound, but I can solve the RoutingError.
For cancan I use this inside application_controller.rb
rescue_from CanCan::AccessDenied do
render template: 'errors/error_403', status: 403
end
and I have this in my routes:
match "/404", to: "errors#error_404", via: :all
If I do the same thing for RoutingError it won't work.
I've also tried match '*path', :to => "errors#error_404" but I get erors.
How can I solve this?
Edit: If I do the same thing for RoutingError as for access denied:
rescue_from ActionController::RoutingError do
render template: 'errors/error_404', status: 404
end
it won't work.
The ActionController::RoutingError is raised when Rails tries to match the request with a route. This happens before Rails even initializes a controller - thus your ApplicationController never has a chance to rescue the exception.
Instead the Rails default exceptions_app kicks in - note that this is an app in the Rack sense - it takes a ENV hash with a request and returns a response - in this case the static /public/404.html file.
What you can do is have your Rails app handle rendering the error pages dynamically instead:
# config/application.rb
config.exceptions_app = self.routes # a Rack Application
# config/routes.rb
match "/404", :to => "errors#not_found", :via => :all
match "/500", :to => "errors#internal_server_error", :via => :all
You would then setup a specific controller to handle the error pages - don't do this in your ApplicationController class as you would be adding a not_found and internal_server_error method to all your controllers!
class ErrorsController < ActionController::Base
protect_from_forgery with: :null_session
def not_found
render(status: 404)
end
def internal_server_error
render(status: 500)
end
end
Code borrowed from Matt Brictson: Dynamic Rails Error Pages - read it for the full rundown.
There is a better way to do it:
routes.rb
Rails.application.routes.draw do
match '*unmatched', to: 'application#route_not_found', via: :all
end
application_controller.rb
class ApplicationController < ActionController::Base
def route_not_found
render file: Rails.public_path.join('404.html'), status: :not_found, layout: false
end
end
To test locally, set the following and restart server.
config/development.rb
config.consider_all_requests_local = false
Tested with Rails 6.
Related
Apparently rescue_from is supposed to catch Exceptions, but this does not work as expected:
class ApplicationController < ActionController::Base
rescue_from ActionController::RoutingError, with: :not_found
def not_found
text: 'Not found'
end
end
Spec:
specify 'Not found' do
visit '/zzz'
expect(page.status_code).to eq 200
end
Failure/Error: visit '/zzz'
ActionController::RoutingError:
No route matches [GET] "/zzz"
Same behavior in development environment.
However, rescuing other errors such as RuntimeError does work as expected.
Docs: https://apidock.com/rails/v6.0.0/ActiveSupport/Rescuable/ClassMethods/rescue_from
Rails 6.0.2
Why can't RoutingError be used with rescue_from? Is RoutingError raised in middleware or by the router before the controller is called? Is there another way to catch RoutingError?
Router errors are raised before reaching a controller, you could have a wildcard route to match anything that's not matched by other routes at the end of the routes.rb file an point that route to a specific controller action.
Something like:
match '*foo', to: 'application#not_found'
(Didn't try that, you may need to tweak it a little but I think the idea is clear)
Building a small app and I'd like to intercept error pages.
Is it possible to define a controller where all of Rails's failed page requests end up so I can add my custom behavior there?
Place this at the bottom of your app config/routes.rb
match '*path', to: redirect('/'), via: :all
Correct me if I'm wrong, but I think rescuing the error from the controller won't work because the request hasn't hit any controller yet for you to do a rescue_from. (Just tried this on 4.2.x)
You will need to add a catch-all kind of route into your config/routes.rb, right at the end of the all the others. This basically routes all the traffic that is not recognized to the errors#show action.
YourApp::Application.routes.draw do
# ...
# ...
get '*path', to: 'errors#show', via: :all
end
Then create the ErrorsController and the method for you to parse the URL.
class ErrorsController < ApplicationController
def show
# parse the URL and redirect or simply render the page
end
end
Do note that the above will not help with ActiveRecord::RecordNotFound errors; because they fall into a route pattern for a specific controller. For that, you can add rescue_from in the ApplicationController as such:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :foo
def foo
# redirect or do something
end
end
I have custom error pages in a Rails 4 application and a method in ApplicationController that I use in certain places to manually raise a RoutingError:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
The ErrorsController has a file_not_found action with a corresponding view. routes.rb has the following content:
match '/404', to: 'errors#file_not_found', via: :all
All of this allows me to write code such as not_found unless item.available in my controllers to force a 404.
I'm currently getting the following error only when I call not_found from the admin namespace: Missing template [project path]/public/404.html. How can I use not_found from admin using the same views?
I ommitted a detail that I didn't think was relevant but turned out to be crucial: I am using rails_admin. While perusing the source for this excellent gem, I noticed that it defines its own not_found method here:
def not_found
render :file => Rails.root.join('public', '404.html'), :layout => false, :status => 404
end
To override it, I added the following code in config/initializers/rails_admin_not_found.rb:
RailsAdmin::ApplicationController.module_eval do
def not_found
raise ActionController::RoutingError.new('Not Found')
end
end
I'm using Devise with Ruby on Rails.
What is the recommended way to redirect unauthenticated users to the sessions#new page if they attempt to access a page that requires authentication?
Right now I get an error that says no route matches the one they attempt to access (leading to a 404 error in production).
Just simple add this method to application_controller.rb
protected
def authenticate_user!
if user_signed_in?
super
else
redirect_to login_path, :notice => 'if you want to add a notice'
## if you want render 404 page
## render :file => File.join(Rails.root, 'public/404'), :formats => [:html], :status => 404, :layout => false
end
end
And you can call this method on before_filter another controllers you want.
e.g :
class HomesController < ApplicationController
before_filter :authenticate_user!
## if you want spesific action for require authentication
## before_filter :authenticate_user!, :only => [:action1, :action2]
end
Don't forget add login_path into routes.rb
devise_scope :user do
match '/sign-in' => "devise/sessions#new", :as => :login
end
note : I always use this way when play with devise for my apps authentication.. (rails 3.2 and rails 4.0.1)
You can do just like GeekTol wrote, or just put
before_action :authenticate_user!
in your controller.
In this case, devise uses the default authenticate_user! method, that will redirect to the "user_session_path" and use the default flash message.
It's not necessary to rewrite authenticate_user! method, unless you want to customize it.
I thought you could just add:
before_action :authenticate_user!
to each controller that required the user to be logged in.
I'm a Rails beginner but I found this in my own searches and it works well in my application.
You should refer to Devise's own How To: How To: Redirect to a specific page when the user can not be authenticated.
Another alternative I can think of is creating a routing Constraint wrapping your protected routes. You'd better stick to Devise's way, but here is an example:
#On your routes.rb
constraints(Constraints::LoginRequired) do
get '/example'
end
#Somewhere like lib/constraints/login_required.rb
module Constraints
class LoginRequired
def self.matches?(request)
#some devise code that checks if the user is logged in
end
end
end
Add this code in your config/routes.rb devise_for :users and resources :users and you can generate devise in views.
I'm trying to handle 404 errors when using a cms gem. In my past application this 404 method seemed to work but now its not rescuing the error. Any ideas?
# application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
rescue_from ActionController::RoutingError, :with => :render_404
def after_sign_in_path_for(resource)
admin_cms_path
end
private
def render_404(exception = nil)
if exception
logger.info "Rendering 404: #{exception.message}"
end
flash[:error] = 'The page you entered is unavailble, please send us an email if this is unexpected'
redirect_to root_path
end
end
and the routes
# routes.rb
Amskier::Application.routes.draw do
# ...
comfy_route :blog_admin, path: '/admin'
comfy_route :cms_admin, path: '/admin'
root to: "cms/content#show"
comfy_route :cms, path: '/', sitemap: false
end
Add the route to this action of controller:
match 'path' to: 'errors#catch_404'
and create errors controller like this
class ErrorsController < ApplicationController
def catch_404
raise ActionController::RoutingError.new(params[:path])
end
end
after this it should start working.