Come redirect a un 404 in Rails?

Mi piacerebbe ‘falsificare’ una pagina 404 in Rails. In PHP, vorrei semplicemente inviare un’intestazione con il codice di errore in quanto tale:

header("HTTP/1.0 404 Not Found"); 

Come è fatto con Rails?

Non rendere te stesso 404, non c’è motivo di farlo; Rails ha già questa funzionalità integrata. Se vuoi mostrare una pagina 404, crea un metodo render_404 (o not_found come l’ho chiamato) in ApplicationController come questo:

 def not_found raise ActionController::RoutingError.new('Not Found') end 

Rails gestisce anche AbstractController::ActionNotFound e ActiveRecord::RecordNotFound allo stesso modo.

Questo fa due cose meglio:

1) Usa Rails ‘built in rescue_from handler per eseguire il rendering della pagina 404 e 2) interrompe l’esecuzione del tuo codice, permettendoti di fare cose carine come:

  user = User.find_by_email(params[:email]) or not_found user.do_something! 

senza dover scrivere brutte dichiarazioni condizionali.

Come bonus, è anche super facile da gestire nei test. Ad esempio, in un test di integrazione di rspec:

 # RSpec 1 lambda { visit '/something/you/want/to/404' }.should raise_error(ActionController::RoutingError) # RSpec 2+ expect { get '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError) 

E minitest:

 assert_raises(ActionController::RoutingError) do get '/something/you/want/to/404' end 

Stato HTTP 404

Per restituire un’intestazione 404, usa semplicemente l’opzione :status per il metodo render.

 def action # here the code render :status => 404 end 

Se si desidera eseguire il rendering della pagina standard 404, è ansible estrarre la funzione in un metodo.

 def render_404 respond_to do |format| format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found } format.xml { head :not_found } format.any { head :not_found } end end 

e chiamalo nella tua azione

 def action # here the code render_404 end 

Se vuoi che l’azione esegua il rendering della pagina di errore e si fermi, usa semplicemente un’istruzione return.

 def action render_404 and return if params[:something].blank? # here the code that will never be executed end 

ActiveRecord e HTTP 404

Ricorda inoltre che Rails salva alcuni errori di ActiveRecord, come ActiveRecord::RecordNotFound mostra la pagina di errore 404.

Significa che non è necessario salvare questa azione da soli

 def show user = User.find(params[:id]) end 

User.find genera un ActiveRecord::RecordNotFound quando l’utente non esiste. Questa è una funzionalità molto potente. Guarda il seguente codice

 def show user = User.find_by_email(params[:email]) or raise("not found") # ... end 

Puoi semplificarlo delegando a Rails il controllo. Basta usare la versione bang.

 def show user = User.find_by_email!(params[:email]) # ... end 

La risposta appena selezionata presentata da Steven Soroka è vicina, ma non completa. Il test in sé nasconde il fatto che questo non restituisce un vero 404 – restituisce uno stato di 200 – “successo”. La risposta originale era più vicina, ma tentava di rendere il layout come se non si fosse verificato alcun errore. Questo risolve tutto:

 render :text => 'Not Found', :status => '404' 

Ecco una mia tipica serie di test per qualcosa che mi aspetto di restituire 404, usando RSPec e Shoulda matchers:

 describe "user view" do before do get :show, :id => 'nonsense' end it { should_not assign_to :user } it { should respond_with :not_found } it { should respond_with_content_type :html } it { should_not render_template :show } it { should_not render_with_layout } it { should_not set_the_flash } end 

Questa sana paranoia mi ha permesso di individuare la mancata corrispondenza del tipo di contenuto quando tutto il resto sembrava peachy 🙂 Controllo tutti questi elementi: variabili assegnate, codice di risposta, tipo di contenuto di risposta, modello reso, layout di rendering, messaggi flash.

Salterò il tipo di contenuto controllo su applicazioni che sono strettamente html … a volte. Dopotutto, “uno scettico controlla TUTTI i cassetti” 🙂

http://dilbert.com/strips/comic/1998-01-20/

Cordiali saluti: io non consiglio di provare per cose che stanno accadendo nel controller, vale a dire “should_raise”. Ciò che ti interessa è l’output. Le mie prove di cui sopra mi hanno permesso di provare varie soluzioni, e i test rimangono gli stessi sia che la soluzione stia generando un’eccezione, un rendering speciale, ecc.

Puoi anche usare il file di rendering:

 render file: "#{Rails.root}/public/404.html", layout: false, status: 404 

Dove puoi scegliere di usare il layout o meno.

Un’altra opzione è usare le eccezioni per controllarlo:

 raise ActiveRecord::RecordNotFound, "Record not found." 

La risposta selezionata non funziona in Rails 3.1+ poiché il gestore degli errori è stato spostato su un middleware (vedere il problema con github ).

Ecco la soluzione che ho trovato di cui sono abbastanza felice.

In ApplicationController :

  unless Rails.application.config.consider_all_requests_local rescue_from Exception, with: :handle_exception end def not_found raise ActionController::RoutingError.new('Not Found') end def handle_exception(exception=nil) if exception logger = Logger.new(STDOUT) logger.debug "Exception Message: #{exception.message} \n" logger.debug "Exception Class: #{exception.class} \n" logger.debug "Exception Backtrace: \n" logger.debug exception.backtrace.join("\n") if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class) return render_404 else return render_500 end end end def render_404 respond_to do |format| format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 } format.all { render nothing: true, status: 404 } end end def render_500 respond_to do |format| format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 } format.all { render nothing: true, status: 500} end end 

e in application.rb :

 config.after_initialize do |app| app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local end 

E nelle mie risorse (mostra, modifica, aggiorna, cancella):

 @resource = Resource.find(params[:id]) or not_found 

Questo potrebbe certamente essere migliorato, ma almeno ho diverse visualizzazioni per not_found e internal_error senza sovrascrivere le funzioni principali di Rails.

questi ti aiuteranno …

Controller dell’applicazione

 class ApplicationController < ActionController::Base protect_from_forgery unless Rails.application.config.consider_all_requests_local rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception } end private def render_error(status, exception) Rails.logger.error status.to_s + " " + exception.message.to_s Rails.logger.error exception.backtrace.join("\n") respond_to do |format| format.html { render template: "errors/error_#{status}",status: status } format.all { render nothing: true, status: status } end end end 

Controller degli errori

 class ErrorsController < ApplicationController def error_404 @not_found_path = params[:not_found] end end 

views / errori / error_404.html.haml

 .site .services-page .error-template %h1 Oops! %h2 404 Not Found .error-details Sorry, an error has occured, Requested page not found! You tried to access '#{@not_found_path}', which is not a valid page. .error-actions %a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path} %span.glyphicon.glyphicon-home Take Me Home 
 <%= render file: 'public/404', status: 404, formats: [:html] %> 

basta aggiungere questo alla pagina che si desidera rendere alla pagina di errore 404 e il gioco è fatto.

Per testare la gestione degli errori, puoi fare qualcosa di simile a questo:

 feature ErrorHandling do before do Rails.application.config.consider_all_requests_local = false Rails.application.config.action_dispatch.show_exceptions = true end scenario 'renders not_found template' do visit '/blah' expect(page).to have_content "The page you were looking for doesn't exist." end end 

Se si desidera gestire 404 diversi in modi diversi, prendere in considerazione la possibilità di prenderli nei controller. Questo ti permetterà di fare cose come rintracciare il numero di 404 generati da diversi gruppi di utenti, avere un supporto per interagire con gli utenti per scoprire cosa è andato storto / quale parte dell’esperienza utente potrebbe aver bisogno di tweaking, fare test A / B, ecc.

Qui ho inserito la logica di base in ApplicationController, ma può anche essere inserito in controller più specifici, per avere una logica speciale solo per un controller.

La ragione per cui sto usando un if con ENV [‘RESCUE_404’], è così che posso testare l’innalzamento di AR :: RecordNotFound in isolamento. Nei test, posso impostare questa variabile ENV su false e il mio rescue_from non viene triggersto. In questo modo posso testare la raccolta separata dalla logica 404 condizionale.

 class ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404'] private def conditional_404_redirect track_404(@current_user) if @current_user.present? redirect_to_user_home else redirect_to_front end end end