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 ?
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 ?
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
Dans test/unit, je crois que c'est quelque chose comme : assert_raises(ActionController::RoutingError) do get '/quelquechose/vous/voulez/to/404' end
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.
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
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
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.
Modifié la réponse sélectionnée pour refléter la meilleure pratique. Merci pour les commentaires !
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.
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
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.
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.
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.
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 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.