Я хотел бы «подделать» страницу 404 в Rails. В PHP я бы просто отправил заголовок с кодом ошибки как таковой:
header("HTTP/1.0 404 Not Found");
Как это сделать с Rails?
Я хотел бы «подделать» страницу 404 в Rails. В PHP я бы просто отправил заголовок с кодом ошибки как таковой:
header("HTTP/1.0 404 Not Found");
Как это сделать с Rails?
Ответы:
Не визуализируйте 404 самостоятельно, нет причин для этого; В Rails эта функциональность уже встроена. Если вы хотите показать страницу 404, создайте render_404
метод (или not_found
как я его назвал) ApplicationController
следующим образом:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rails тоже обрабатывает AbstractController::ActionNotFound
, и так ActiveRecord::RecordNotFound
же.
Это делает две вещи лучше:
1) Он использует встроенный rescue_from
обработчик Rails для рендеринга страницы 404, и 2) он прерывает выполнение вашего кода, позволяя вам делать такие приятные вещи, как:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
без необходимости писать некрасивые условные высказывания.
В качестве бонуса, это также очень легко обрабатывать в тестах. Например, в тесте интеграции 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)
И минитест:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
ИЛИ см. Дополнительную информацию из рендера Rails 404, не найденного в действии контроллера
ActionController::RecordNotFound
это лучший вариант?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
Чтобы вернуть заголовок 404, просто используйте :status
опцию для метода рендеринга.
def action
# here the code
render :status => 404
end
Если вы хотите отобразить стандартную страницу 404, вы можете извлечь функцию в методе.
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
и назовите это в вашем действии
def action
# here the code
render_404
end
Если вы хотите, чтобы действие отобразило страницу с ошибкой и остановилось, просто используйте оператор return.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
Также помните, что Rails спасает некоторые ошибки ActiveRecord, такие как ActiveRecord::RecordNotFound
отображение страницы с ошибкой 404.
Это означает, что вам не нужно спасать это действие самостоятельно
def show
user = User.find(params[:id])
end
User.find
вызывает, ActiveRecord::RecordNotFound
когда пользователь не существует. Это очень мощная функция. Посмотрите на следующий код
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
Вы можете упростить это, делегировав Rails проверку. Просто используйте версию взрыва.
def show
user = User.find_by_email!(params[:email])
# ...
end
Недавно выбранный ответ, представленный Стивеном Сорокой, близок, но не завершен. Сам тест скрывает тот факт, что это не возвращает истинный 404 - он возвращает статус 200 - «успех». Первоначальный ответ был ближе, но попытался отобразить макет, как если бы не произошло никакого сбоя. Это исправляет все:
render :text => 'Not Found', :status => '404'
Вот типичный мой тестовый набор для чего-то, что я ожидаю вернуть 404, используя сопоставители RSpec и 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
Эта здоровая паранойя позволила мне обнаружить несоответствие типов контента, когда все остальное выглядело замечательно :) Я проверяю все эти элементы: назначенные переменные, код ответа, тип контента ответа, представленный шаблон, отображаемый макет, флэш-сообщения.
Я пропущу проверку типа контента в приложениях, которые строго HTML ... иногда. Ведь «скептик проверяет ВСЕ ящики» :)
http://dilbert.com/strips/comic/1998-01-20/
К вашему сведению: я не рекомендую проверять то, что происходит в контроллере, то есть «should_raise». Что вас волнует, так это результат. Мои тесты, приведенные выше, позволили мне попробовать различные решения, и тесты остаются прежними, независимо от того, вызывает ли решение исключение, специальный рендеринг и т. Д.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
параметр true в вашем environments/development.rb
файле. Если вы поднимете ошибку, как описано в принятом решении, в постановке / производстве, вы обязательно получите 404, а не 200.
Вы также можете использовать файл рендеринга:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Где вы можете использовать макет или нет.
Другой вариант - использовать исключения для управления им:
raise ActiveRecord::RecordNotFound, "Record not found."
Выбранный ответ не работает в Rails 3.1+, так как обработчик ошибок был перемещен в промежуточное ПО (см. Проблему с github ).
Вот решение, которое я нашел, которым я очень доволен.
В 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
и в application.rb
:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
И в моих ресурсах (показать, отредактировать, обновить, удалить):
@resource = Resource.find(params[:id]) or not_found
Это, безусловно, можно улучшить, но, по крайней мере, у меня есть разные представления для not_found и internal_error без переопределения основных функций Rails.
|| not_found
часть, просто вызовите find!
(заметьте взрыв), и он выдаст ActiveRecord :: RecordNotFound, когда ресурс не может быть получен. Также добавьте ActiveRecord :: RecordNotFound в массив в условии if.
StandardError
и не Exception
, на всякий случай. На самом деле я оставлю стандартную 500 статическую страницу и не буду использовать пользовательскую render_500
вообще, то есть я буду явно rescue_from
массив ошибок, связанных с 404
это поможет вам ...
Контроллер приложений
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
Контроллер ошибок
class ErrorsController < ApplicationController
def error_404
@not_found_path = params[:not_found]
end
end
просмотров / ошибки / 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] %>
просто добавьте это на страницу, которую вы хотите отобразить на странице ошибки 404, и все готово.
Я хотел выдать «нормальный» 404 для любого вошедшего в систему пользователя, который не является администратором, поэтому я написал что-то подобное в 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
Чтобы проверить обработку ошибок, вы можете сделать что-то вроде этого:
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
Если вы хотите обрабатывать разные 404 по-разному, подумайте о том, чтобы перехватить их в своих контроллерах. Это позволит вам выполнять такие действия, как отслеживание числа 404, сгенерированного различными группами пользователей, поддержка взаимодействия с пользователями, чтобы выяснить, что пошло не так / какая часть пользовательского опыта может потребоваться настроить, выполнить A / B-тестирование и т. Д.
Здесь я поместил базовую логику в ApplicationController, но ее также можно поместить в более конкретные контроллеры, чтобы иметь специальную логику только для одного контроллера.
Причина, по которой я использую if с ENV ['RESCUE_404'], заключается в том, что я могу проверить поднятие AR :: RecordNotFound изолированно. В тестах я могу установить для этого ENV var значение false, и мой rescue_from не сработает. Таким образом, я могу проверить повышение отдельно от условной логики 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