Необходимо вернуть JSON-форматированную ошибку 404 в Rails

У меня есть обычный интерфейс HTML и JSON API в моем приложении Rails. Теперь, если кто-то вызывает /api/not_existent_method.json, он возвращает страницу HTML 404 по умолчанию. Есть ли способ изменить это на что-то вроде {"error": "not_found"}, оставив исходную 404-страничку для наружного интерфейса HTML?

Ответ 1

Друг указал мне на элегантное решение, которое обрабатывает не только 404, но и 500 ошибок. Фактически, он обрабатывает каждую ошибку. Ключ состоит в том, что каждая ошибка генерирует исключение, которое распространяется вверх через стек промежуточных сред стойки, пока не будет обработано одним из них. Если вам интересно узнать больше, вы можете смотреть этот отличный скринкаст. У Rails есть собственные обработчики для исключений, но вы можете переопределить их с помощью менее документированного параметра конфигурации exceptions_app. Теперь вы можете написать свое собственное промежуточное программное обеспечение, или вы можете перенаправить ошибку обратно в рельсы, например:

# In your config/application.rb
config.exceptions_app = self.routes

Затем вам просто нужно сопоставить эти маршруты в config/routes.rb:

get "/404" => "errors#not_found"
get "/500" => "errors#exception"

И тогда вы просто создаете контроллер для обработки этого.

class ErrorsController < ActionController::Base
  def not_found
    if env["REQUEST_PATH"] =~ /^\/api/
      render :json => {:error => "not-found"}.to_json, :status => 404
    else
      render :text => "404 Not found", :status => 404 # You can render your own template here
    end
  end

  def exception
    if env["REQUEST_PATH"] =~ /^\/api/
      render :json => {:error => "internal-server-error"}.to_json, :status => 500
    else
      render :text => "500 Internal Server Error", :status => 500 # You can render your own template here
    end
  end
end

Последнее, что нужно добавить: В среде разработки рельсы обычно не отображают 404 или 500 страниц, но вместо этого печатают обратную линию. Если вы хотите увидеть свой ErrorsController в действии в режиме разработки, отключите материал backtrace в файле config/enviroments/development.rb.

config.consider_all_requests_local = false

Ответ 2

Мне нравится создавать отдельный контроллер API, который устанавливает формат (json) и api-специфичные методы:

class ApiController < ApplicationController
  respond_to :json

  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  # Use Mongoid::Errors::DocumentNotFound with mongoid

  def not_found
    respond_with '{"error": "not_found"}', status: :not_found
  end
end

Тест RSpec:

  it 'should return 404' do
    get "/api/route/specific/to/your/app/", format: :json
    expect(response.status).to eq(404)
  end

Ответ 3

Конечно, он будет выглядеть примерно так:

class ApplicationController < ActionController::Base
  rescue_from NotFoundException, :with => :not_found
  ...

  def not_found
    respond_to do |format|
      format.html { render :file => File.join(Rails.root, 'public', '404.html') }
      format.json { render :text => '{"error": "not_found"}' }
    end
  end
end

NotFoundException не является реальным именем исключения. Он будет отличаться от версии Rails и точного поведения, которое вы хотите. Довольно легко найти с помощью поиска Google.

Ответ 4

Попробуйте добавить в конец вашего routes.rb:

match '*foo', :format => true, :constraints => {:format => :json}, :to => lambda {|env| [404, {}, ['{"error": "not_found"}']] }