2012-04-20 14 views
69

Mam normalny interfejs HTML i interfejs API JSON w mojej aplikacji Rails. Teraz, jeśli ktoś zadzwoni pod numer /api/not_existent_method.json, zwróci domyślną stronę HTML 404. Czy jest jakiś sposób, aby zmienić to na coś podobnego do {"error": "not_found"}, pozostawiając oryginalną stronę 404 nienaruszonego frontendu HTML?Konieczność zwrócenia błędu 404 w formacie JSON w Railsach

Odpowiedz

103

przyjaciel wskazał mi w kierunku eleganckie rozwiązanie, które nie tylko obsłużyć 404, ale także 500 błędów. W rzeczywistości obsługuje każdy błąd. Kluczem jest to, że każdy błąd generuje wyjątek, który rozprzestrzenia się w górę przez stos middleware, aż zostanie obsłużony przez jeden z nich. Jeśli chcesz dowiedzieć się więcej, możesz obejrzeć this excellent screencast. Railsy mają własne procedury obsługi wyjątków, ale możesz je zastąpić mniej udokumentowaną opcją konfiguracji exceptions_app. Teraz można napisać własny middleware czy można trasa błąd powrotem do szyn, tak:

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

Wtedy po prostu trzeba dopasować te trasy w swojej config/routes.rb:

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

a następnie po prostu utwórz kontroler do obsługi tego.

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 

Ostatnią rzeczą, aby dodać: W środowisku programistycznym, szyny usally nie czyni 404 lub 500 stron, ale drukuje ślad zamiast. Jeśli chcesz zobaczyć swoją ErrorsController w akcji w trybie programowania, wyłącz opcję śledzenia wstecznego w pliku config/enviroments/development.rb.

config.consider_all_requests_local = false 
+4

Nie zapomnij także dodać kodu stanu do renderowania. W przeciwnym razie twój klient/przeglądarka nie będzie wiedział, że to 404/500. render: text => "404 not found",: status =>: not_found – arnab

+0

Edytowałem swoją odpowiedź. – iblue

+1

Powiedziałbym, że blok respond_to jest bardziej uniwersalny w funkcjach renderowania: respond_to do | format | format.json {render json: {błąd: "nieodnaleziony"}. To_json, status: 404} format.html {wyrenderowanie tekstu: "404 Nie znaleziono", status: 404} koniec –

11

Oczywiście, będzie to wyglądać mniej więcej tak:

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 jest nie prawdziwe imię wyjątku. Będzie się różnić w zależności od wersji Railsów i dokładnego zachowania, które chcesz. Bardzo łatwe do znalezienia dzięki wyszukiwarce Google.

+2

Dzięki za pomysł, ale Używam Rails 3.2.2, gdzie obsługa wyjątków uległa zmianie. To już nie zadziała. – iblue

+0

@iblue Działa to dobrze w Railsach 3.2+ i jest lepszym rozwiązaniem. Zobacz 'rescue_from' [docs] (http://apidock.com/rails/v3.2.3/ActiveSupport/Rescuable/ClassMethods/rescue_from), aby uzyskać więcej informacji. – Intelekshual

+0

Działa tylko dla błędów 404, a nie dla 500 błędów. – iblue

4

Spróbuj umieścić na koniec twoich routes.rb:

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

Dla każdego, kto to czyta, 'match' w pliku' Rails.application.routes' wymaga teraz określenia metody HTTP za pomocą parametru 'via:'. Będzie to również wyświetlane jako błąd podczas próby użycia powyższej linii. – sameers

13

Chciałbym utworzyć oddzielny kontroler interfejsu API, który ustawia format (json) oraz metod API-specyficzne:

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 testową:

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

To wydaje się działać tylko dla rekordów. Jak zarządzałbyś sprawą dla 'api/non_existant_route'? – Sebastialonso

+0

wiersz 'rescue_from ActiveRecord :: RecordNotFound, z: not_found' musi być' with:: not_found' ale jest to tylko jedna edycja znaku: P – erroric

+0

@erroric thanks! :) –

Powiązane problemy