516 votes

Comment rediriger vers un 404 dans Rails ?

J'aimerais "simuler" une page 404 dans Rails. En PHP, j'enverrais simplement un en-tête avec le code d'erreur comme tel :

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

Comment cela se fait-il avec Rails ?

1098voto

Steven Soroka Points 8446

Ne rendez pas la page 404 vous-même, il n'y a aucune raison de le faire ; Rails a déjà intégré cette fonctionnalité. Si vous voulez afficher une page 404, créez un fichier render_404 (ou not_found comme je l'ai appelé) dans ApplicationController comme ça :

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

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

Cela fait deux choses de mieux :

1) Il utilise la fonction intégrée de Rails rescue_from pour rendre la page 404, et 2) il interrompt l'exécution de votre code, vous permettant de faire des choses agréables comme :

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

sans avoir à écrire d'affreuses déclarations conditionnelles.

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

Ou bien, pour plus d'informations, consultez le site Rails rend 404 not found à partir d'une action de contrôleur

0 votes

Dans test/unit, je crois que c'est quelque chose comme : assert_raises(ActionController::RoutingError) do get '/quelquechose/vous/voulez/to/404' end

3 votes

Il y a une raison de le faire soi-même. Si votre application détourne toutes les routes de la racine. C'est une mauvaise conception, mais parfois inévitable.

0 votes

Ne pas utiliser render_404 et retourner si params[:something].blank ?

265voto

Simone Carletti Points 77653

Statut HTTP 404

Pour renvoyer un en-tête 404, il suffit d'utiliser la fonction :status pour la méthode de rendu.

def action
  # here the code

  render :status => 404
end

Si vous voulez rendre 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 l'appeler dans votre action

def action
  # here the code

  render_404
end

Si vous voulez que l'action affiche la page d'erreur et s'arrête, utilisez simplement une instruction de retour.

def action
  render_404 and return if params[:something].blank?

  # here the code that will never be executed
end

ActiveRecord et HTTP 404

N'oubliez pas non plus que Rails récupère certaines erreurs ActiveRecord, telles que l'erreur ActiveRecord::RecordNotFound en 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 soulève un ActiveRecord::RecordNotFound lorsque l'utilisateur n'existe pas. Il s'agit d'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 à Rails la vérification. Utilisez simplement la version bang.

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

9 votes

Cette solution pose un gros problème : elle continue à exécuter le code du modèle. Ainsi, si vous disposez d'une structure simple et reposante et que quelqu'un saisit un ID qui n'existe pas, votre modèle recherchera l'objet qui n'existe pas.

5 votes

Comme mentionné précédemment, ce n'est pas la bonne réponse. Essayez celle de Steven.

0 votes

Modifié la réponse sélectionnée pour refléter la meilleure pratique. Merci pour les commentaires !

61voto

Jaime Bellmyer Points 13815

La réponse nouvellement sélectionnée soumise par Steven Soroka est proche, mais pas complète. Le test lui-même cache le fait qu'il ne renvoie pas un vrai 404 - il renvoie un statut de 200 - "succès". La réponse originale était plus proche, mais a essayé de rendre la mise en page comme si aucun échec ne s'était produit. Ceci corrige tout :

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

Voici un jeu d'essai typique pour quelque chose qui devrait renvoyer 404, en utilisant les correspondances 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 repérer l'inadéquation du type de contenu alors que tout le reste semblait parfait :) Je vérifie tous ces éléments : variables assignées, code de réponse, type de contenu de la réponse, modèle rendu, mise en page rendue, messages flash.

Je ne vérifie pas le type de contenu pour les applications qui sont strictement html... parfois. Après tout, "un sceptique vérifie TOUS les tiroirs" :)

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

Pour info : 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 restent les mêmes, que la solution consiste à lever une exception, un rendu spécial, etc.

3 votes

J'aime beaucoup cette réponse, surtout en ce qui concerne le test de la sortie et non des méthodes appelées dans le contrôleur

1 votes

Rails a un statut 404 intégré : render :text => 'Not Found', :status => :not_found .

1 votes

JaimeBellmyer - Je suis certain que oui. pas retourner un 200 lorsque vous êtes dans un environnement déployé (c'est-à-dire staging / prod). Je fais cela dans plusieurs applications et cela fonctionne comme décrit dans la solution acceptée. Vous faites peut-être allusion au fait que l'écran de débogage renvoie une valeur de 200 lorsqu'il est affiché dans l'environnement de développement, où vous avez probablement l'adresse de l'utilisateur. config.consider_all_requests_local défini à true dans votre environments/development.rb fichier. Si vous soulevez une erreur, comme décrit dans la solution acceptée, dans la mise en scène/production, vous obtiendrez certainement un 404, et non un 200.

16voto

Augustin Riedinger Points 1600

La réponse choisie ne fonctionne pas dans Rails 3.1+ car le gestionnaire d'erreurs a été déplacé vers un intergiciel (cf. numéro github ).

Voici la solution que j'ai trouvée et dont je suis assez satisfait.

Sur 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 en 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 (afficher, modifier, mettre à jour, supprimer) :

@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 avoir à remplacer les fonctions de base de Rails.

3 votes

Il s'agit d'une très bonne solution, mais vous n'avez pas besoin de l'outil de gestion de l'environnement. || not_found il suffit d'appeler find! (remarquez le bang) et il lancera ActiveRecord::RecordNotFound si la ressource ne peut être récupérée. Ajoutez également ActiveRecord::RecordNotFound au tableau dans la condition if.

1 votes

Je sauverais StandardError et non Exception juste au cas où. En fait, je vais laisser la page standard 500 statique et ne pas utiliser la page personnalisée. render_500 du tout, ce qui signifie que je vais explicitement rescue_from matrice d'erreurs liées à 404

7voto

Caner Çakmak Points 157

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

vues/erreurs/erreur_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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X