Parinha
Parinha

Reputation: 95

how to handle ActiveRecord::RecordNotFound in both ActionController::API and ActionController::Base

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

ActionController::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

ActionController::Base

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

Answers (2)

Jordan Pickwell
Jordan Pickwell

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

moyinho20
moyinho20

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

Related Questions