Chciałbym „sfałszować” stronę 404 w Railsach. W PHP po prostu wysyłam nagłówek z kodem błędu jako takim:
header("HTTP/1.0 404 Not Found");
Jak to się robi z Railsami?
Chciałbym „sfałszować” stronę 404 w Railsach. W PHP po prostu wysyłam nagłówek z kodem błędu jako takim:
header("HTTP/1.0 404 Not Found");
Jak to się robi z Railsami?
Odpowiedzi:
Nie renderuj 404 samemu, nie ma powodu; Railsy mają już tę funkcję wbudowaną. Jeśli chcesz wyświetlić stronę 404, utwórz render_404
metodę (lub not_found
jak ją nazwałem) w ApplicationController
następujący sposób:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Poręcze również obsługują AbstractController::ActionNotFound
iActiveRecord::RecordNotFound
ten sam sposób.
To robi dwie rzeczy lepiej:
1) Używa wbudowanego rescue_from
modułu obsługi Railsów do renderowania strony 404, i 2) przerywa wykonywanie kodu, pozwalając ci robić fajne rzeczy, takie jak:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
bez konieczności pisania brzydkich instrukcji warunkowych.
Jako bonus, jest również bardzo łatwy w obsłudze w testach. Na przykład w teście integracji 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)
I najmniejszy:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
LUB odnieś więcej informacji z Railsów, render 404 nie znaleziono z działania kontrolera
ActionController::RecordNotFound
jest lepsza opcja?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
Aby zwrócić nagłówek 404, po prostu użyj :status
opcji metody renderowania.
def action
# here the code
render :status => 404
end
Jeśli chcesz wyrenderować standardową stronę 404, możesz wyodrębnić funkcję w metodzie.
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
i nazwij to w swoim działaniu
def action
# here the code
render_404
end
Jeśli chcesz, aby akcja wyświetlała stronę błędu i zatrzymywała się, po prostu użyj instrukcji return.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
Pamiętaj również, że Railsy ratują niektóre błędy ActiveRecord, takie jak ActiveRecord::RecordNotFound
wyświetlanie strony błędu 404.
Oznacza to, że nie musisz samodzielnie ratować tej akcji
def show
user = User.find(params[:id])
end
User.find
podnosi, ActiveRecord::RecordNotFound
gdy użytkownik nie istnieje. To bardzo potężna funkcja. Spójrz na następujący kod
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
Możesz to uprościć, delegując czek do Rails. Po prostu użyj wersji Bang.
def show
user = User.find_by_email!(params[:email])
# ...
end
Nowo wybrana odpowiedź przesłana przez Stevena Sorokę jest bliska, ale niepełna. Sam test ukrywa fakt, że nie zwraca prawdziwego 404 - zwraca status 200 - „sukces”. Oryginalna odpowiedź była bliższa, ale próbowała renderować układ tak, jakby nie wystąpiła awaria. To naprawia wszystko:
render :text => 'Not Found', :status => '404'
Oto typowy mój zestaw testowy dla czegoś, co spodziewam się zwrócić 404, przy użyciu dopasowań RSpec i 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
Ta zdrowa paranoja pozwoliła mi dostrzec niedopasowanie typu zawartości, gdy wszystko inne wyglądało brzoskwiniowo :) Sprawdzam wszystkie te elementy: przypisane zmienne, kod odpowiedzi, typ treści odpowiedzi, renderowany szablon, renderowany układ, wiadomości flash.
Pominę sprawdzanie typu zawartości w aplikacjach, które są ściśle HTML ... czasami. W końcu „sceptyk sprawdza WSZYSTKIE szuflady” :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: Nie polecam testowania rzeczy, które dzieją się w kontrolerze, tj. „Powinien_jasować”. To, na czym Ci zależy, to wynik. Moje powyższe testy pozwoliły mi wypróbować różne rozwiązania, a testy pozostają takie same, niezależnie od tego, czy rozwiązanie generuje wyjątek, specjalne renderowanie itp.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
ustawiony parametr true w swoim environments/development.rb
pliku. Jeśli zgłaszasz błąd, jak opisano w przyjętym rozwiązaniu, w inscenizacji / produkcji, na pewno dostaniesz 404, a nie 200.
Możesz także użyć pliku renderowania:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Gdzie możesz użyć układu, czy nie.
Inną opcją jest użycie wyjątków, aby to kontrolować:
raise ActiveRecord::RecordNotFound, "Record not found."
Wybrana odpowiedź nie działa w Rails 3.1+, ponieważ program obsługi błędów został przeniesiony do oprogramowania pośredniego (patrz problem z github ).
Oto rozwiązanie, z którego jestem zadowolony.
W 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
oraz w application.rb
:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
I w moich zasobach (pokaż, edytuj, aktualizuj, usuń):
@resource = Resource.find(params[:id]) or not_found
Można to z pewnością poprawić, ale przynajmniej mam inne widoki dla not_found i internal_error bez nadpisywania podstawowych funkcji Railsów.
|| not_found
części, po prostu zadzwoń find!
(zauważ huk), a wyrzuci ActiveRecord :: RecordNotFound, gdy nie będzie można odzyskać zasobu. Dodaj również ActiveRecord :: RecordNotFound do tablicy w warunku if.
StandardError
i nie na Exception
wszelki wypadek. Właściwie zostawię standardową stronę statyczną 500 i w ogóle nie render_500
rescue_from
te pomogą ci ...
Kontroler aplikacji
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
Kontroler błędów
class ErrorsController < ApplicationController
def error_404
@not_found_path = params[:not_found]
end
end
views / error / 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] %>
po prostu dodaj to do strony, którą chcesz wyrenderować na stronie błędu 404 i gotowe.
Chciałem rzucić „normalny” 404 dla każdego zalogowanego użytkownika, który nie jest administratorem, więc ostatecznie napisałem coś takiego w 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
Aby przetestować obsługę błędów, możesz zrobić coś takiego:
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
Jeśli chcesz obsługiwać różne 404 na różne sposoby, rozważ złapanie ich w kontrolerach. Umożliwi to wykonywanie czynności takich jak śledzenie liczby 404 wygenerowanych przez różne grupy użytkowników, wsparcie w interakcji z użytkownikami, aby dowiedzieć się, co poszło nie tak / jaka część doświadczenia użytkownika może wymagać ulepszenia, testowania A / B itp.
Umieściłem tutaj podstawową logikę w ApplicationController, ale można ją również umieścić w bardziej szczegółowych kontrolerach, aby mieć specjalną logikę tylko dla jednego kontrolera.
Powodem, dla którego używam if z ENV [„RESCUE_404”], jest to, że mogę przetestować podnoszenie AR :: RecordNotFound w izolacji. W testach mogę ustawić ten ENV var na false, a mój rescue_from nie będzie strzelał. W ten sposób mogę przetestować podniesienie niezależnie od logiki warunkowej 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