Имеет ли Rails "исключенное" исключение?

Я пишу приложение, которое использует простые старые объекты Ruby (POROs) для абстрактной логики авторизации из контроллеров.

В настоящее время у меня есть собственный класс исключений, называемый NotAuthorized который я rescue_from на уровне контроллера, но мне было любопытно узнать: появилось ли в Rails 4 исключение, указывающее, что действие не было санкционировано? Я заново изобретаю колесо, выполняя это исключение?

Уточнение: raise AuthorizationException не происходит нигде внутри контроллера, оно происходит внутри полностью развязанного PORO вне контроллера. Объект не знает HTTP, маршрутов или контроллеров.

Ответ 1

Rails, похоже, не отображает исключение :unauthorized.

Сопоставления по умолчанию определяются в activerecord/lib/active_record/railtie.rb:

config.action_dispatch.rescue_responses.merge!(
  'ActiveRecord::RecordNotFound'   => :not_found,
  'ActiveRecord::StaleObjectError' => :conflict,
  'ActiveRecord::RecordInvalid'    => :unprocessable_entity,
  'ActiveRecord::RecordNotSaved'   => :unprocessable_entity
)

и actionpack/lib/action_dispatch/middleware/exception_wrapper.rb:

@@rescue_responses.merge!(
  'ActionController::RoutingError'             => :not_found,
  'AbstractController::ActionNotFound'         => :not_found,
  'ActionController::MethodNotAllowed'         => :method_not_allowed,
  'ActionController::UnknownHttpMethod'        => :method_not_allowed,
  'ActionController::NotImplemented'           => :not_implemented,
  'ActionController::UnknownFormat'            => :not_acceptable,
  'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
  'ActionDispatch::ParamsParser::ParseError'   => :bad_request,
  'ActionController::BadRequest'               => :bad_request,
  'ActionController::ParameterMissing'         => :bad_request
)

Вы можете добавить настраиваемое исключение из вашей конфигурации приложения (или пользовательской версии Railtie):

Your::Application.configure do

  config.action_dispatch.rescue_responses.merge!(
    'AuthorizationException' => :unauthorized
  )

  # ...

end

Или просто используйте rescue_from.

Ответ 2

Я предполагаю, что Rails не представила это исключение, потому что авторизация и аутентификация не являются собственными поведением Rails (не считая basicauth, конечно).

Обычно это обязанности других библиотек Devise for NotAuthenticated; Pundit, CanCanCan, Rollify для NotAuthorized) Я бы на самом деле утверждал, что может быть неплохо расширить ActionController с пользовательскими исключениями, такими как ActionController::NotAuthorized (потому что, как я уже сказал, это не ответственность)

Итак, как я обычно занимаюсь этой проблемой, я ввел пользовательские исключения на ApplicationController

class ApplicationController  < ActionController::Base
  NotAuthorized = Class.new(StandardError)
  # ...or if you really want it to be ActionController
  # NotAuthorized = Class.new(ActionController::RoutingError)

  rescue_from ActiveRecord::RecordNotFound do |exception|
    render_error_page(status: 404, text: 'Not found')
  end

  rescue_from ApplicationController::NotAuthorized do |exception|
    render_error_page(status: 403, text: 'Forbidden')
  end

  private

  def render_error_page(status:, text:, template: 'errors/routing')
    respond_to do |format|
      format.json { render json: {errors: [message: "#{status} #{text}"]}, status: status }
      format.html { render template: template, status: status, layout: false }
      format.any  { head status }
    end
  end
end

Поэтому в моих контроллерах я могу

class MyStuff < ApplicationController
  def index
    if current_user.admin?
      # ....
    else 
      raise ApplicationController::NotAuthorized
    end
  end
end

Это четко определяет, что слой, ожидающий, что это исключение будет поднято и поймано, - это ваш прикладной уровень, а не сторонний lib.

Дело в том, что библиотеки могут меняться (и да, это означает, что Rails тоже), определяющие исключение на сторонних классах lib и спасая их на вашем прикладном уровне, действительно опасны, как если бы значение класса исключений rescue_from его, это тормозит ваш rescue_from

Вы можете прочитать много статей, в которых люди Waring о Rails raise - rescue_from являются современными goto (теперь рассматривают анти-шаблон среди некоторых экспертов), и в определенной степени это правда, но только если вы спасаете Исключения, которые у вас нет полный контроль off !!

Это означает, что сторонние исключения (включая Devise и Rails до определенной точки). Если вы определяете классы исключений в своем приложении, вы не ретранслируете на третьей стороне lib => у вас есть полный контроль => вы можете rescue_from без этого анти-шаблона.