Custom 404 not firing from controller - ruby-on-rails

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.

Related

rescue_from ActionController::RoutingError doesn't work

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.

Invalid Authenticity Token on Post

I am using devise to sign up/in.
routes
get 'profile' => 'profile#get_profile'
post 'profile' => 'profile#create_profile'
and profile_controller
def get_profile
render json: {user: current_user}, status: :ok
end
def create_profile
render json: {user: current_user}, status: :ok
end
GET: http://localhost:3000/user/profile returns the expected output. However,
POST request throws an error saying:
ActionController::InvalidAuthenticityToken in User::ProfileController#create_profile.
Please demystify this behavior.
To disable CSRF protection you can edit your ApplicationControllerlike this:
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
# ...
end
or disable the CSRF protection for specific controller:
class ProfilesController < ApplicationController
skip_before_action :verify_authenticity_token
# ...
end
:null_session strategy empties the session instead of raising an exception which is perfect for an API. Because the session is empty, you can't use current_user method or othes helpers that refer to the session.
IMPORTANT:
protect_from_forgery with: :null_session must be used only in specific
cases, for example to allow API request (POST/PUT/PATCH/DELETE) without html form
With protect_from_forgery with: :null_session you must restrict access to your data with an authorization system because every one could do request against your API endpoint
Don't remove protect_from_forgery with: :exception for requests that are done through html form, is dangerous! (read here http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)
To handle both standard requests (through html form) and API requests generally you have to set up two different controller for the same resource. Example:
Routes
Rails.application.routes.draw do
resources :profiles
namespace :api do
namespace :v1 do
resources :profiles
end
end
end
ApplicationController
# app/controllers/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
end
ProfilesController
(standard controller for html requests)
# app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
# POST yoursites.com/profiles
def create
end
end
Api::V1::ProfilesController
(controller for API requests)
# app/controllers/api/v1/profiles_controller.rb
module Api
module V1
class ProfilesController < ApplicationController
# To allow only json request
protect_from_forgery with: :null_session, if: Proc.new {|c| c.request.format.json? }
# POST yoursites.com/api/v1/profiles
def create
end
end
end
end
refereces:
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html#method-i-protect_from_forgery
Get requests don't have an authenticity token.
You will have to add the request forgery stuff to your forms using this
<%= csrf_meta_tag %>
And address via javascript
$('meta[name="csrf-token"]')
In ApplicationController (or another controller your controllers inherit from) there's a line:
protect_from_forgery with: :exception
Remove it and CSRF checks will be disabled.

Rendering custom error page from admin namespace

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

How to catch and redirect rails routes that are valid, but invalid resource id?

match '*path' => redirect('/'), via: :all if Rails.env.production? handles things nicely, but it doesn't properly catch cases like this
/root.com/articles/293 where 293 is an article id that doesn't exist in the database.
In this case, it still redirects to the default 404 page, which on heroku is the ugly "something went wrong" page.
How can I tap into the " valid url, but invalid resource id " url to control its redirect to where I want?
Check out rescue_from. It's quite handy when you want to deviate from Rails' default behavior of displaying a 404 page.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
# handle redirect
end
end
Seems to me like this is a check you would do in your controller. Something along the lines of:
class ArticlesController < ApplicationController
def show
id = params[:id]
if Article.exists?(id)
#proceed as normal
else
#redirect to "Article doesn't exist" page
end
end
end
You could create a generic method like this:
class ApplicationController < ActionController::Base
def redirect_if_does_not_exist
id = params[:id]
model_name = controller_name.classify.constantize
unless model_name.exists?(id)
# handle redirect
end
end
Then you could just call this method in a before_action callback on the controllers that you want to check. Like this:
class ArticlesController < ApplicationController
before_action :redirect_if_does_not_exist
end

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