Reputation: 95
In this post, the errors are rescued in the both api and base controller methods. But it might not be best approach to handle errors because of some reasons are:
In ActionController::Base, we handled ActiveRecord::RecordNotFound in only ApplicationController. But for ActionController::API i have to rescue ActiveRecord::RecordNotFound in every controller. So are there any best approach for handle this problem?
Using Rails 5 and 'active_model_serializers' gem for api
module Api
module V1
class UsersController < ActionController::API
before_action :find_user, only: :show
def find_user
@user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.to_s }, status: :not_found
end
end
end
end
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render file: "#{Rails.root}/public/404", layout: true, status: :not_found
end
end
Upvotes: 0
Views: 3394
Reputation: 163
ActionController::API
includes the ActionController::Rescue
module which is what provides the rescue_from
class method.
I would create an Api::BaseController
base class that the Api::V1::UsersController
can use instead of using ActionController::API
on each controller class. This would allow you have a rescue_from
in a single place instead of needing a rescue
block on every action.
module Api
class BaseController < ActionController::API
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
private
def handle_error(e)
render json: { error: e.to_s }, status: :bad_request
end
end
module V1
class UsersController < BaseController
def find_user
@user = User.find(params[:id])
end
end
end
end
I'd also further create an Api::V1::BaseController
to allow for easier versioning of the APIs. Then, if you decide to change the format of the errors for v2, just move the rescue_from
in the Api::BaseController
to the Api::V1::BaseController
, and add a new rescue_from
to the new Api::V2::BaseController
.
module Api
class CommonBaseController < ActionController::API
# code common across API versions
end
module V1
class BaseController < CommonBaseController
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
private
def handle_error(e)
render json: { error: e.to_s }, status: :bad_request
end
end
end
module V2
class BaseController < CommonBaseController
# use a custom base error class to make catching errors easier and more standardized
rescue_from BaseError, with: :handle_error
rescue_from ActiveRecord::RecordNotFound, with: :handle_error
private
def handle_error(e)
status, status_code, code, title, detail =
if e.is_a?(ActiveRecord::RecordNotFound)
[:not_found, '404', '104', 'record not found', 'record not found']
else
[
e.respond_to?(:status) ? e.status : :bad_request,
e.respond_to?(:status_code) ? e.status_code.to_s : '400',
e.respond_to?(:code) ? e.code.to_s : '100',
e.respond_to?(:title) ? e.title : e.to_s,
e.respond_to?(:detail) ? e.detail : e.to_s
]
end
render(
json: {
status: status_code,
code: code,
title: title,
detail: detail
},
status: status
)
end
end
end
end
Upvotes: 0
Reputation: 624
You can do something like this in application_controller.rb
if Rails.env.production?
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
def render_404
render json: {meta: meta_response(404, "Record not found")}
end
This would rescue all RecordNotFound exception with 404 but only in production mode.
Upvotes: 1