Comment redirect vers un 404 dans Rails?

Je voudrais «faire semblant» une page 404 dans Rails. En PHP, je voudrais juste envoyer un en-tête avec le code d’erreur en tant que tel:

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

Comment ça se passe avec Rails?

Ne te rends pas 404, il n’y a pas de raison; Rails a déjà cette fonctionnalité intégrée. Si vous voulez afficher une page 404, créez une méthode render_404 (ou not_found comme je l’ai appelée) dans ApplicationController comme ceci:

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

Rails gère également AbstractController::ActionNotFound et ActiveRecord::RecordNotFound la même manière.

Cela fait deux choses mieux:

1) Il utilise le gestionnaire rescue_from intégré à rescue_from pour rendre la page 404, et 2) il interrompt l’exécution de votre code, vous permettant de faire de belles choses comme:

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

sans avoir à écrire des instructions conditionnelles laides.

En prime, il est également très facile à manipuler lors des tests. Par exemple, dans un test d’intégration 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) 

Et minitest:

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

Statut HTTP 404

Pour retourner un en-tête 404, utilisez simplement l’option :status pour la méthode de rendu.

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

Si vous souhaitez afficher la page 404 standard, vous pouvez extraire la fonctionnalité dans une méthode.

 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 

et appelez-le dans votre action

 def action # here the code render_404 end 

Si vous souhaitez que l’action affiche la page d’erreur et s’arrête, utilisez simplement une instruction return.

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

ActiveRecord et HTTP 404

Rappelez-vous également que Rails récupère certaines erreurs ActiveRecord, telles que ActiveRecord::RecordNotFound affichant la page d’erreur 404.

Cela signifie que vous n’avez pas besoin de sauver cette action vous-même

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

User.find déclenche un ActiveRecord::RecordNotFound lorsque l’utilisateur n’existe pas. C’est une fonctionnalité très puissante. Regardez le code suivant

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

Vous pouvez le simplifier en déléguant le chèque à Rails. Utilisez simplement la version bang.

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

La nouvelle réponse sélectionnée par Steven Soroka est proche, mais incomplète. Le test lui-même cache le fait que cela ne retourne pas un vrai 404 – il renvoie un statut de 200 – “succès”. La réponse initiale était plus proche, mais a tenté de rendre la mise en page comme si aucune défaillance ne s’était produite. Cela corrige tout:

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

Voici un jeu de test typique pour quelque chose que je compte renvoyer 404, en utilisant les matchers RSpec et 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 

Cette saine paranoïa m’a permis de déceler la non-concordance de type de contenu lorsque tout le monde semblait en pleine forme 🙂 Je vérifie tous ces éléments: variables affectées, code de réponse, type de contenu de réponse, rendu de modèle, rendu flash.

Je vais sauter le contrôle du type de contenu sur les applications ssortingctement HTML … parfois. Après tout, “un sceptique vérifie TOUS les tiroirs” 🙂

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

FYI: Je ne recommande pas de tester les choses qui se passent dans le contrôleur, par exemple “should_raise”. Ce qui vous intéresse, c’est la sortie. Mes tests ci-dessus m’ont permis d’essayer différentes solutions, et les tests restnt les mêmes, que la solution soulève une exception, un rendu spécial, etc.

Vous pouvez également utiliser le fichier de rendu:

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

Où vous pouvez choisir d’utiliser la mise en page ou non.

Une autre option consiste à utiliser les exceptions pour le contrôler:

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

La réponse sélectionnée ne fonctionne pas dans Rails 3.1+, car le gestionnaire d’erreurs a été déplacé vers un middleware (voir problème github ).

Voici la solution que j’ai trouvée qui me satisfait assez bien.

Dans 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 

et dans application.rb :

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

Et dans mes ressources (show, edit, update, delete):

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

Cela pourrait certainement être amélioré, mais au moins, j’ai des vues différentes pour not_found et internal_error sans écraser les fonctions Rails.

ceux-ci vous aideront …

Contrôleur d’application

 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 

Contrôleur d'erreurs

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

views / errors / 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 sortinged 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] %> 

ajoutez simplement ceci à la page que vous voulez rendre à la page d’erreur 404 et vous avez terminé.

Pour tester la gestion des erreurs, vous pouvez faire quelque chose comme ceci:

 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 

Si vous souhaitez gérer différents 404 de différentes manières, envisagez de les capturer dans vos contrôleurs. Cela vous permettra de faire le suivi du nombre de 404 générés par différents groupes d’utilisateurs, d’interagir avec les utilisateurs pour savoir ce qui a mal tourné / quelle partie de l’expérience utilisateur pourrait nécessiter des ajustements, des tests A / B, etc.

J’ai ici placé la logique de base dans ApplicationController, mais elle peut également être placée dans des contrôleurs plus spécifiques, pour n’avoir qu’une logique particulière pour un seul contrôleur.

La raison pour laquelle j’utilise un if avec ENV [‘RESCUE_404’] est que je peux tester la génération d’AR :: RecordNotFound de manière isolée. Dans les tests, je peux définir cette variable ENV sur false et my rescue_from ne se déclenche pas. De cette façon, je peux tester la relance séparément de la logique 404 conditionnelle.

 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