Пользовательская обработка ошибок в Rails 4.0

Я создаю API Ruby on Rails, используя Ruby 2.0 и Rails 4.0. Мое приложение представляет собой почти исключительно JSON API, поэтому, если возникает ошибка (500, 404), я хочу зафиксировать эту ошибку и вернуть красиво отформатированное сообщение об ошибке JSON.

Я пробовал это, а также:

rescue_from ActionController::RoutingError, :with => :error_render_method

def error_render_method
  puts "HANDLING ERROR"
  render :json => { :errors => "Method not found." }, :status => :not_found
  true
end

В моем ApplicationController.

Ни один из них не помогает (исключения вообще не фиксируются). Мой поиск в Google показывает, что это сильно изменилось между версиями 3.1 и 3.2, и я не могу найти никакой хорошей документации о том, как это сделать в Rails 4.0.

Кто-нибудь знает?

Изменить Вот трассировка стека при переходе на страницу 404:

Started GET "/testing" for 127.0.0.1 at 2013-08-21 09:50:42 -0400

ActionController::RoutingError (No route matches [GET] "/testing"):
actionpack (4.0.0) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'
actionpack (4.0.0) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
railties (4.0.0) lib/rails/rack/logger.rb:38:in `call_app'
railties (4.0.0) lib/rails/rack/logger.rb:21:in `block in call'
activesupport (4.0.0) lib/active_support/tagged_logging.rb:67:in `block in tagged'
activesupport (4.0.0) lib/active_support/tagged_logging.rb:25:in `tagged'
activesupport (4.0.0) lib/active_support/tagged_logging.rb:67:in `tagged'
railties (4.0.0) lib/rails/rack/logger.rb:21:in `call'
actionpack (4.0.0) lib/action_dispatch/middleware/request_id.rb:21:in `call'
rack (1.5.2) lib/rack/methodoverride.rb:21:in `call'
rack (1.5.2) lib/rack/runtime.rb:17:in `call'
activesupport (4.0.0) lib/active_support/cache/strategy/local_cache.rb:83:in `call'
rack (1.5.2) lib/rack/lock.rb:17:in `call'
actionpack (4.0.0) lib/action_dispatch/middleware/static.rb:64:in `call'
railties (4.0.0) lib/rails/engine.rb:511:in `call'
railties (4.0.0) lib/rails/application.rb:97:in `call'
rack (1.5.2) lib/rack/lock.rb:17:in `call'
rack (1.5.2) lib/rack/content_length.rb:14:in `call'
rack (1.5.2) lib/rack/handler/webrick.rb:60:in `service'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/webrick/httpserver.rb:138:in `service'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/webrick/httpserver.rb:94:in `run'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/webrick/server.rb:295:in `block in start_thread'


Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/routes/_route.html.erb (2.9ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/routes/_route.html.erb (0.9ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/routes/_table.html.erb (1.1ms)
Rendered /Library/Ruby/Gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (38.3ms)

Я не думаю, что хочу, чтобы это когда-либо зашло так далеко, что-то должно поймать его и вернуть соответствующий ответ об ошибке json.


person Ethan Mick    schedule 21.08.2013    source источник
comment
Хмммм, это может на самом деле работать, как я его настроил... Позвольте мне проверить это еще немного.   -  person Ethan Mick    schedule 23.08.2013


Ответы (5)


Запрос даже не попадает в ваше приложение.

Вам нужно определить общий маршрут, чтобы Rails отправлял запрос в ваше приложение, а не отображал ошибку (в разработке) или отображал страницу public/404.html (в производстве).

Измените файл route.rb, включив в него следующее.

match "*path", to: "errors#catch_404", via: :all

И в вашем контроллере

class ErrorsController < ApplicationController

  def catch_404
    raise ActionController::RoutingError.new(params[:path])
  end
end

И тогда ваш rescue_from должен поймать ошибку.

person silasjmatson    schedule 11.12.2013
comment
Вы можете изменить get в маршрутах на match, так как это может произойти и для других методов запроса. - person dylanfm; 22.01.2014
comment
Rails 4 напомнит вам, чтобы вы предоставили свое действие как GET, так и POST, [путем] добавления[ing] via: [:get, :post] — не забудьте сделать это тоже! :) - person sameers; 06.07.2014

Попробовав несколько вариантов, я остановился на этом как на самом простом способе обработки API 404:

# Passing request spec
describe 'making a request to an unrecognised path' do
  before { host! 'api.example.com' }
    it 'returns 404' do
    get '/nowhere'
    expect(response.status).to eq(404)
  end
end

# routing
constraints subdomain: 'api' do
  namespace :api, path: '', defaults: { format: 'json' } do
    scope module: :v1, constraints: ApiConstraints.new(1) do
      # ... actual routes omitted ...
    end
    match "*path", to: -> (env) { [404, {}, ['{"error": "not_found"}']] }, via: :all
  end
end
person tsdbrown    schedule 01.08.2014
comment
+1 за сохранение его в файле маршрутов и добавление теста. Я использовал это более или менее дословно в своем приложении, спасибо! - person Joost Baaij; 16.10.2014

это работает в rails4, таким образом вы можете напрямую управлять всеми ошибками: например, вы можете отображать error_info как json, когда возникает ошибка при вызове API.

application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery


  # CUSTOM EXCEPTION HANDLING
  rescue_from StandardError do |e|
    error(e)
  end

  def routing_error
    raise ActionController::RoutingError.new(params[:path])
  end

  protected

  def error(e)
    #render :template => "#{Rails::root}/public/404.html"
    if env["ORIGINAL_FULLPATH"] =~ /^\/api/
    error_info = {
      :error => "internal-server-error",
      :exception => "#{e.class.name} : #{e.message}",
    }
    error_info[:trace] = e.backtrace[0,10] if Rails.env.development?
    render :json => error_info.to_json, :status => 500
    else
      #render :text => "500 Internal Server Error", :status => 500 # You can render your own template here
      raise e
    end
  end

  # ...

end

routes.rb

MyApp::Application.routes.draw do

  # ...

  # Any other routes are handled here (as ActionDispatch prevents RoutingError from hitting ApplicationController::rescue_action).
  match "*path", :to => "application#routing_error", :via => :all
end
person magnum    schedule 01.09.2013

Я использовал 404.html из общей папки, и это в среде разработки.
На самом деле я получил ответ от:

Тем не менее, я провел небольшой эксперимент с тем, какие фрагменты кода действительно заставляют его работать. Вот фрагменты кода, которые я только добавил.

config/routes.rb

Rails.application.routes.draw do
    // other routes
    match "*path", to: "application#catch_404", via: :all
end

приложение/контроллеры/application_controller.rb

class ApplicationController < ActionController::Base
    def catch_404
        render :file => 'public/404.html', :status => :not_found
    end
end

Буду признателен за любые комментарии и разъяснения относительно того, почему некоторые из оригиналов необходимы. Например, используя эту строку кода

raise ActionController::RoutingError.new(params[:path])

и это

rescue_from ActionController::RoutingError, :with => :error_render_method

Потому что rescue_from и raise ActionController::RoutingError кажутся популярным ответом из старых версий Rails.

person noelvictorino    schedule 09.09.2014

Попробуйте это, если вы хотите одинаково реагировать на все типы ошибок.

rescue_from StandardError, :with => :error_render_method

Если вы не хотите такого поведения в режиме разработки, добавьте приведенный выше код в

unless Rails.application.config.consider_all_requests_local

person usha    schedule 21.08.2013
comment
Полезно знать, но похоже, что это вообще не улавливает ошибку (я добавил трассировку стека). Мой метод error_render_method никогда не вызывается. - person Ethan Mick; 21.08.2013
comment
Я использую рельсы 3.2.13, и это работает для меня. Может быть, они изменили что-то вокруг этого в рельсах 4 - person usha; 21.08.2013
comment
Это не будет вызываться в нескольких разных случаях, если вы неправильно включили его в свой ActionController. Из документов: обработчик первого класса, для которого exception.is_a?(klass) имеет значение true, вызывается, если таковой имеется - person WattsInABox; 21.08.2013