Vorrei "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 si fa con Rails?
Vorrei "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 si fa con Rails?
Risposte:
Non renderti 404 te stesso, non c'è motivo di farlo; Rails ha già questa funzionalità integrata. Se vuoi mostrare una pagina 404, crea un render_404
metodo (o not_found
come l'ho chiamato) in ApplicationController
questo modo:
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) Utilizza il rescue_from
gestore integrato di Rails per eseguire il rendering della pagina 404 e 2) interrompe l'esecuzione del 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 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 più minuscolo:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
OPPURE fare riferimento a maggiori informazioni da Rails render 404 non trovato da un'azione del controller
ActionController::RecordNotFound
sia l'opzione migliore?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
Per restituire un'intestazione 404, basta usare l' :status
opzione per il metodo di rendering.
def action
# here the code
render :status => 404
end
Se si desidera eseguire il rendering della pagina 404 standard, è possibile 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 renda la 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
Ricorda inoltre che Rails salva alcuni errori ActiveRecord, come la ActiveRecord::RecordNotFound
visualizzazione della 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. Usa semplicemente la versione bang.
def show
user = User.find_by_email!(params[:email])
# ...
end
La risposta appena selezionata inviata da Steven Soroka è vicina, ma non completa. Il test stesso nasconde il fatto che questo non sta restituendo un vero 404 - sta restituendo uno stato di 200 - "successo". La risposta originale era più vicina, ma tentò di renderizzare il layout come se non si fosse verificato alcun errore. Questo risolve tutto:
render :text => 'Not Found', :status => '404'
Ecco un mio tipico set di test per qualcosa che mi aspetto di restituire 404, usando i matcher RSpec e Shoulda:
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 discrepanza 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 reso, messaggi flash.
Salterò il controllo del tipo di contenuto su applicazioni strettamente HTML ... a volte. Dopotutto, "uno scettico controlla TUTTI i cassetti" :)
http://dilbert.com/strips/comic/1998-01-20/
Cordiali saluti: Non raccomando di testare cose che stanno accadendo nel controller, cioè "should_raise". Quello che ti interessa è l'output. I miei test sopra mi hanno permesso di provare varie soluzioni, e i test rimangono gli stessi sia che la soluzione stia sollevando un'eccezione, un rendering speciale, ecc.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
parametro impostato su true nel tuo environments/development.rb
file. Se si genera un errore, come descritto nella soluzione accettata, nella messa in scena / produzione, si otterrà sicuramente un 404, non un 200.
È inoltre possibile utilizzare il file di rendering:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Dove puoi scegliere di usare il layout o no.
Un'altra opzione è quella di utilizzare 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 in un middleware (vedi problema con github ).
Ecco la soluzione che ho trovato di cui sono abbastanza contento.
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, elimina):
@resource = Resource.find(params[:id]) or not_found
Questo potrebbe certamente essere migliorato, ma almeno ho viste diverse per not_found e internal_error senza sovrascrivere le funzioni di base di Rails.
|| not_found
parte, basta chiamare find!
(notare il botto) e genererà ActiveRecord :: RecordNotFound quando non è possibile recuperare la risorsa. Inoltre, aggiungi ActiveRecord :: RecordNotFound all'array nella condizione if.
StandardError
e non Exception
, per ogni evenienza. In realtà lascerò la pagina statica 500 standard e non userò affatto l'abitudine render_500
, il che significa che esplicitamente rescue_from
schiererò una serie di errori relativi a 404
questi ti aiuteranno ...
Controller 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 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.
Volevo lanciare un 404 "normale" per qualsiasi utente connesso che non fosse un amministratore, quindi ho finito per scrivere qualcosa di simile in Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, 'Not Found'
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
Per testare la gestione degli errori, puoi fare qualcosa del genere:
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 vuoi gestire 404 diversi in modi diversi, prendi in considerazione di catturarli nei tuoi controller. Ciò ti consentirà di fare cose come tracciare il numero di 404 generati da diversi gruppi di utenti, avere il supporto interagire con gli utenti per scoprire cosa è andato storto / quale parte dell'esperienza utente potrebbe aver bisogno di modifiche, fare test A / B, ecc.
Ho inserito la logica di base in ApplicationController, ma può anche essere posizionata in controller più specifici, per avere una logica speciale solo per un controller.
Il motivo per cui sto usando un if con ENV ['RESCUE_404'], è così che posso testare il sollevamento di AR :: RecordNotFound in isolamento. Nei test, posso impostare questo var ENV su false e il mio rescue_from non si attiva. In questo modo posso testare il sollevamento separato 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