как обрабатывать ActiveRecord::RecordNotFound как в ActionController::API, так и в ActionController::Base

В этом посте ошибки спасены как в методах API, так и в базовом контроллере. Но это может быть не лучший подход к обработке ошибок по следующим причинам:

  • Толстые контроллеры
  • СУХОЙ
  • Ремонтопригодность

В ActionController::Base мы обрабатывали ActiveRecord::RecordNotFound только в ApplicationController. Но для ActionController::API мне нужно спасать ActiveRecord::RecordNotFound в каждом контроллере. Итак, есть ли лучший подход для решения этой проблемы?

Использование Rails 5 и гема «active_model_serializers» для 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:: База

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

person Parinha    schedule 31.05.2017    source источник
comment
@icemelt это имеет для меня смысл :) Любое другое решение?   -  person Parinha    schedule 31.05.2017


Ответы (2)


Вы можете сделать что-то подобное в 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

Это спасет все исключения RecordNotFound с ошибкой 404, но только в рабочем режиме.

person moyinho20    schedule 31.05.2017

ActionController::API включает модуль ActionController::Rescue, который предоставляет метод класса rescue_from.

Я бы создал базовый класс Api::BaseController, который может использовать Api::V1::UsersController, вместо использования ActionController::API для каждого класса контроллера. Это позволит вам иметь rescue_from в одном месте, вместо того, чтобы нуждаться в блоке rescue для каждого действия.

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

Я бы также создал Api::V1::BaseController, чтобы упростить управление версиями API. Затем, если вы решите изменить формат ошибок для v2, просто переместите rescue_from в Api::BaseController на Api::V1::BaseController и добавьте новый rescue_from в новый 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
person Jordan Pickwell    schedule 30.11.2018