[Rails] Exception handling #2

В одной из своих заметок я рассказывал о том, как правильно отдавать ошибки и статусы для запросов в разных форматах. Недавно наткнулся на небольшое дополнение, оно давно уже не ново, но для общего развития будет полезно.

В ранней статье не описывалась обработка ошибки авторизации. Если пользователь пытается войти без токена, то внутри модуля с этой проверкой, происходит возврат статуса //403// без каких либо дополнительных сообщений:

module ApiHelper
  def require_auth
    head :forbidden unless current_client
  end
  
  def current_client
    @client ||= Client.find_by_api_token(params[:api_token])
  end
end

Но вместе с ним в заголовках вернется и Content-Type, который не всегда должен быть markdown/html.

Content-Type: markdown/html; charset=utf-8\r\nStatus: 403 Forbidden

Это сломает вашего клиента, который к примеру написан с использованием Weary.

Правильный способ

Есть хорошая статья, в которой говорится, что лучше всего рассказывать пользователю о том, что он неправ через исключения. Т.е. наилучшим сигналом будет не запись в лог проблем, а именно бросание исключения. Логи далеко не все читают, а из тех, кто читает - умеющих его нормально разбирать и понимать очень и очень мало.

Переделаем немного наш модуль:

def require_auth
  raise ApplicationController::Forbidden unless current_client
end

В главном ApplicationController пропишем

class ApplicationController < ActionController::Base
  class Forbidden < StandardError; end
end

в config/application.rb добавляем:

config.action_dispatch.rescue_responses.merge!(
  'ApplicationController::Forbidden' => :forbidden
)

routes.rb:

match "/403" => "errors#forbidden"

Добавляем написанный в предыдущей заметке errors_controller метод forbidden

def forbidden
  @body_msg = "Access Denied"
  render status: :forbidden
end

Далее добавляем вью с нужным нам форматом вывода и после запроса получаем:

Content-Type: application/json; charset=utf-8\r\nStatus: 403 Forbidden
{ "message": "Access Denied" }

То, что нужно! Таким образом можно обработать любую исключительную ситуацию и возвращать разные сообщения и статусы в нужных местах. Просто добавьте исключение, обработчик и пропишите в роутах и application.rb нужное исключение, создайте вью с правильным форматом ответа и радуйтесь жизни)

UPD: в комментариях добавили про 4ые рельсы и матч, вношу поправку, если вы пишите на 4х, то в роутах должно быть:
routes.rb:

match "/403" => "errors#forbidden", via: :get