Как я могу повторно вызвать исключение Ruby в операторе Rails rescue_from?

Приложение My Rails 4 использует RocketPants для своего JSON API и Pundit для авторизации.

В моем /app/controllers/api/v1/base_controller.rb файле есть код для обработки ошибок от Pundit. Когда пользователь не авторизован для обновления ресурса, Pundit выдает NotAuthorizedError исключение, и я спасаю его своим user_not_authorized методом:

class API::V1::BaseController < RocketPants::Base
  include Pundit
  version 1

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  def user_not_authorized
    error! :forbidden
  end

end

Когда я вызываю метод error!, который RocketPants предоставляет из моего обработчика исключений, Я ожидаю получить такой ответ JSON:

{
  "error":             "forbidden",
  "error_description": "The requested action was forbidden."
}

Однако вместо этого вызов error! сразу же взрывает запрос:

Completed 500 Internal Server Error in 143ms

RocketPants::Forbidden - RocketPants::Forbidden:
  rocket_pants (1.13.1) lib/rocket_pants/controller/error_handling.rb:44:in `error!'
  app/controllers/api/v1/base_controller.rb:61:in `user_not_authorized'

Полная трассировка стека здесь.

Почему метод error! не выполняет то, что должен при вызове из моего обработчика исключений Pundit?

Если я поставлю error! :forbidden в середине действия моего контроллера, он будет работать, как ожидалось.

Для контекста контроллер, который наследуется от base_controller.rb и вызывает метод authorize Pundit, выглядит так:

class API::V1::MealsController < API::V1::BaseController

  before_filter :find_entity

  def create
    meal = @entity.meals.build(meal_params)

    authorize(@entity, :update?)

    if meal.save
      expose meal, status: :created
    else
      expose meal.errors, status: 422
    end
  end

end

person Rob Sobers    schedule 18.05.2016    source источник


Ответы (1)


Очевидно, создание исключений в rescue_from - плохая идея, и, согласно документации Rails, исключения, возникающие в обработчике, не всплывают:

Исключения, возникающие внутри обработчиков исключений, не распространяются.

Документы: http://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html

Вместо того, чтобы повторно вызывать исключение RocketPants, я просто сам создаю и возвращаю сообщение об ошибке JSON:

  def user_not_authorized
    # error! :forbidden
    head 403
    error = { error: 'Action not allowed.', error_description: 'Sorry, you are not allowed to perform this action.'}
    expose error
  end

Это работает!

ОБНОВЛЕНИЕ

С тех пор я нашел еще более чистое решение: просто сопоставьте исключение Pundit с исключением RocketPants. Это означает, что всякий раз, когда возникает Pundit::NotAuthorizedError ошибка, она будет рассматриваться как RocketPants::Forbidden ошибка.

Получил все решение до одной строчки кода в верхней части base_controller.rb:

  map_error! Pundit::NotAuthorizedError, RocketPants::Forbidden

Обработчик не требуется.

person Rob Sobers    schedule 18.05.2016