Active Admin custom 400, 500 error pages - ruby-on-rails

How would you implement custom 400 and 500 error pages in active admin? The pages must use the active admin layout.

into your routes:
match "/404", to: "errors#render_error"
match "/500", to: "errors#render_error"
build new controller called ErrorsController
class ErrorsController < ApplicationController
def render_error
exception = env["action_dispatch.exception"]
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
#method, #message = if status_code == 404
["not_found", env["REQUEST_URI"]]
else
["server_error", "#{exception.message}\n#{exception.backtrace.join('\n')}"]
end
render status: status_code
end
end
then in your ApplicationController:
private
def not_found
raise ActionController::RoutingError.new('Not Found')
end
then create new view to show any html you want.
hope it helps you.

Related

How can I render a custom JSON error response "Couldn't find Plan with id" in Rails?

I would like to render a custom error that shows when the user cannot find the class 'Plan' by id. The problem is that it's not reaching the if statement.
NB. I am using Insomnia to test it.
class Api::V1::PlansController < Api::V1::BaseController
before_action :authorize_request
def show
#plan = Plan.find(params[:id])
if #plan.errors.any?
render json 'wrong'
else
#accounts = #plan.accounts
render json: #plan, status: :created
end
end
end
ActiveRecord::FinderMethods#find will raise an ActiveRecord::RecordNotFound exception when if one the ids cannot be found. And when an exception is raised it halts execution.
You can handle the exception by using rescue_from:
# Do not use the scope resolution operator when declaring classes and modules
# #see https://github.com/rubocop-hq/ruby-style-guide#namespace-definition
module Api
module V1
class PlansController < BaseController
before_action :authorize_request
rescue_from ActiveRecord::RecordNotFound, with: :not_found
def show
#plan = Plan.find(params[:id])
render json: #plan
end
private
def not_found
render json: { error: 'not found' }, status: :not_found
end
end
end
end
The recommendation to use find_by might sound like a good idea initially until you realize that the exception is really useful as it halts execution of the action and prevents nil errors.
module Api
module V1
class PlansController < BaseController
# ...
before_action :set_plan
def update
# this would create a nil error if we had used 'find_by'
if #plan.update(plan_params)
# ...
else
# ...
end
end
private
def set_plan
#plan = Plan.find(params[:id])
end
end
end
end
Using rescue_from is also a really powerful pattern as it lets you move error handling up in the inheritance chain instead of repeating yourself:
module Api
module V1
class BaseController < ::ActionController::Api
rescue_from ActiveRecord::RecordNotFound, with: :not_found
private
def not_found
render json: { error: 'not found' }, status: :not_found
end
end
end
end
But most likely you don't even need this at all in the first place. Rails rescues ActiveRecord::RecordNotFound on the framework level by sending a 404 - Not Found response. Clients should not need any more context then the status code in this case and returning completely unessicary JSON error message responses is an anti-pattern.

Rails 4; Raise an error and catch within controller

I'd love to define a rescue_from handler in my controller to respond to the error.
modudle Api
module V1
class TreesController < Api::V1::ApiController
rescue_from TreeNotFound, with: :missing_tree
def show
#tree = find_tree
end
private
def missing_tree(error)
redirect_to(action: :index, flash: error.message)
end
def find_tree
find_forest.trees.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise TreeNotFound, "Couldn't find a tree to hug"
end
end
end
end
However I got some error Api::V1::TreesController::TreeNotFound.
Any idea?
Update
# api_controller.rb
module Api
module V1
class ApiController < JSONAPI::ResourceController
skip_before_action :verify_authenticity_token # Disable CSRF to enable to function as API
respond_to :json
# NOTE: This block is used when you put unrelated values
rescue_from(ArgumentError) do |e|
render json: { error: e.message }, states: 400 # :bad_request
end
rescue_from(ActionController::ParameterMissing) do |e|
error = {}
error[e.param] = ['parameter is required']
response = { errors: [error] }
render json: response, status: 422 # :unprocessable_entity
end
end
end
end
you need to declare the error class first before you can use it. Do this by inheriting from StandardError.
class TreeNotFound < StandardError
end

404 status: missing layout

When record is not found I render 404 page. The problem is it's doesn't have application layout although 403 works fine
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :render_404
def render_404
render file: 'public/404.html', status: 404, layout: 'application'
end
def render_403
render file: 'public/403.html', status: 403, layout: 'application'
end
end
Are you sure your custom rescue_from is being executed?.. I don't think so.
Maybe another exception is thrown, not ActiveRecord::RecordNotFound.
The thing is that public/404.html is rendered for 404 error by rails by default, with no layout.
If you want to tweak this behavior, remove that public/* files and put them under app/views folder, so you have full control and rails default behavior won't confuse you.
We have a better way of capturing exceptions:
(Here's where we got it from)
Capture
A much more efficient way of capturing exceptions is to use the exceptions_app method
#config/environments/production.rb
config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }
--
Process
Secondly, you should process the caught exception. You can do this by sending the request to a controller method (we use ExceptionController#show):
#app/controllers/exception_controller.rb
class ExceptionController < ApplicationController
#Response
respond_to :html, :xml, :json
#Dependencies
before_action :status
#Layout
layout :layout_status
####################
# Action #
####################
#Show
def show
respond_with status: #status
end
####################
# Dependencies #
####################
protected
#Info
def status
#exception = env['action_dispatch.exception']
#status = ActionDispatch::ExceptionWrapper.new(env, #exception).status_code
#response = ActionDispatch::ExceptionWrapper.rescue_responses[#exception.class.name]
end
#Format
def details
#details ||= {}.tap do |h|
I18n.with_options scope: [:exception, :show, #response], exception_name: #exception.class.name, exception_message: #exception.message do |i18n|
h[:name] = i18n.t "#{#exception.class.name.underscore}.title", default: i18n.t(:title, default: #exception.class.name)
h[:message] = i18n.t "#{#exception.class.name.underscore}.description", default: i18n.t(:description, default: #exception.message)
end
end
end
helper_method :details
####################
# Layout #
####################
private
#Layout
def layout_status
#status.to_s == "404" ? "application" : "error"
end
end
--
Show
Finally, you can output the message you've received, with custom layouts per error:
#app/views/exception/show.html.erb
<div class="box">
<h1><%= details[:name] %></h1>
<p><%= details[:message] %></p>
</div>

How to avoid multiple redirect_to when called inside a method

I've got a little issue with rails, I want to be able to do something like this to avoid multiple redirections :
def render_not_found
not_found
end
private
def not_found
redirect_to website_url(#website), :status => 301 and return return
end
return return doesn't work of course!
Using: rails 3.2.0
There's a few ways to do this. One way is to define and raise a custom error, and have a handler that redirects when that happens.
application_controller.rb
Class ApplicationController < ActionController::Base
around_filter :catch_errors
def catch_errors
yield
rescue SiteNotFoundError
redirect_to website_url(#website), :status => 301
rescue ActiveRecord::RecordNotFound
render 404
rescue ...
...
...
end
end
class SiteNotFoundError < StandardError; end
in your controller
def your_action
raise SiteNotFoundError if (some condition)
end
or in a before filter
before_filter :ensure_valid_site
def ensure_valid_site
raise SiteNotFoundError if ....
end
I usually put my redirections for errors in before_filters.
But if you really want to do this, you can do it like this... but I'm warning you
it ain't pretty.
def render_not_found
not_found(binding)
end
private
def not_found(b)
redirect_to website_url(#website), :status => 301
b.eval('return')
end

Best way to handle 404 in Rails3 controllers with a DataMapper get

It's very simple, I want to handle a normal [show] request with a call to DataMapper like I did in Merb.
With ActiveRecord I could have done this:
class PostsController
def show
#post = Post.get(params[:id])
#comments = #post.comments unless #post.nil?
end
end
and it handles the 404 by catching the resource's exceptions.
DataMapper instead doesn't do this automatically so right now I'm solving it with this solution:
[moved in the answers]
It is possible to tell the controller to halt inside the not_found function?
I like to use exception throwing, and then use ActionController's rescue_from.
Example:
class ApplicationController < ActionController::Base
rescue_from DataMapper::ObjectNotFoundError, :with => :not_found
def not_found
render file => "public/404.html", status => 404, layout => false
end
end
class PostsController
def show
#post = Post.get!(params[:id]) # This will throw an DataMapper::ObjectNotFoundError if it can't be found
#comments = #post.comments
end
end
Done 'the old Merb way':
class ApplicationController
def not_found
render file: "public/404.html", status: 404, layout: false
end
end
class PostsController
def show
#post = Post.get(params[:id])
not_found; return false if #post.nil?
#comments = #post.comments
end
end
again: It is possible to tell the controller to halt inside the not_found function instead of explicitly calling 'return false' in the show action?
edit: thanx to Francois that found a better solution:
class PostsController
def show
#post = Post.get(params[:id])
return not_found if #post.nil?
#comments = #post.comments
end
end
As DM documentation says, you can use #get!

Resources