Comment rediriger vers un 404 en Rails?

j'aimerais "truquer" 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 est-ce fait avec les Rails?

449
demandé sur Andrei Eliade 2010-03-05 12:52:55

9 réponses

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

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

comportent aussi les Rails AbstractController::ActionNotFound et ActiveRecord::RecordNotFound de la même façon.

Ce n'est deux choses mieux:

1) Il utilise des Rails construits dans le manipulateur rescue_from pour rendre La page d'erreur 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 de vilaines déclarations conditionnelles.

en prime, il est également très facile à manipuler dans les 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 reportez-vous plus d'info de Rails de de rendre 404 non trouvé à partir d'un action contrôleur

1012
répondu Steven Soroka 2018-09-27 10:46:37

HTTP 404 Status

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 voulez rendre la norme 404 page, 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 voulez que l'action rende la page d'erreur et s'arrête, il vous suffit d'utiliser 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 aussi que Rails corrige certaines erreurs ActiveRecord, comme le ActiveRecord::RecordNotFound qui affiche la page d'erreur 404.

, Cela signifie que vous n'avez pas besoin pour 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. 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 aux Rails le contrôle. Utilisez simplement la version bang.

def show
  user = User.find_by_email!(params[:email])
  # ...
end
233
répondu Simone Carletti 2014-08-31 09:48:48

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 que ce n'est pas retourner un vrai 404 - c'est retourner un statut de 200 - "succès". La réponse originale était plus proche, mais elle tentait de rendre la mise en page comme si aucun échec ne s'était produit. Cela règle tout:

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

voici un jeu d'essai typique de la mienne pour quelque chose que je m'attends à retourner 404, en utilisant RSpec et 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

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 pour tous ces éléments: variables attribuées, code de réponse, Type de contenu de réponse, rendu de modèle, rendu de mise en page, messages flash.

je vais sauter la vérification de type de contenu sur 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 /

FYI: Je ne recommande pas de tester pour les choses qui se passent dans le contrôleur, c'est à dire"should_raise". Ce que vous vous souciez de la sortie. Mes tests ci-dessus m'ont permis d'essayer différentes solutions, et les tests restent les mêmes que la solution soulève une exception, un rendu spécial, etc.

56
répondu Jaime Bellmyer 2011-08-17 20:58:17

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 ou non la disposition.

une autre option consiste à utiliser les Exceptions pour la contrôler:

raise ActiveRecord::RecordNotFound, "Record not found."
16
répondu Paulo Fidalgo 2015-01-19 15:41:52

la réponse sélectionnée ne fonctionne pas dans les Rails 3.1+ car le gestionnaire d'erreurs a été déplacé dans un middleware (voir GitHub issue ).

Voici la solution que j'ai trouvée et qui me satisfait.

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

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

cela pourrait certainement être améliorée,mais au moins, j'ai des vues différentes pour not_found et internal_error sans passer les fonctions de Rails de base.

11
répondu Augustin Riedinger 2014-08-31 09:49:36

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

"151960920 Erreurs" contrôleur

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

vues/erreurs/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
7
répondu Caner Çakmak 2017-08-10 11:51:13
<%= render file: 'public/404', status: 404, formats: [:html] %>

il suffit d'ajouter ceci à la page que vous souhaitez rendre à la page d'erreur 404 et vous avez terminé.

1
répondu Ahmed Reza 2015-11-01 06:42:03

pour tester le traitement 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
0
répondu maprihoda 2014-04-14 20:40:50

si vous voulez manipuler différents 404s de différentes façons, envisagez de les attraper dans vos controllers. Cela vous permettra de faire des choses comme le suivi du nombre de 404s générés par différents groupes d'utilisateurs, avoir le support interagir avec les utilisateurs pour savoir ce qui a mal tourné / quelle partie de l'expérience utilisateur pourrait avoir besoin de peaufinage, faire des tests A/B, etc.

j'ai ici placé la logique de base dans ApplicationController, mais elle peut aussi être placée dans des controllers plus spécifiques, avoir une logique spéciale pour un seul contrôleur.

la raison pour laquelle j'utilise un si avec ENV['RESCUE_404'], est que je peux tester la levée de AR::RecordNotFound en isolation. Dans les tests, je peux mettre ce VAR ENV à false, et mon secours ne tirerait pas. De cette façon, je peux tester l'élévation séparément de la logique conditionnelle 404.

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
0
répondu Houen 2015-07-25 07:15:19