Я хотів би "підробити" 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
Рейки також справляються 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 render 404, не знайденої в результаті дії контролера
ActionController::RecordNotFound
це кращий варіант?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ через 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
Якщо ви хочете, щоб дія відобразила сторінку помилки і зупинилася, просто використовуйте оператор повернення.
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/
FYI: Я не рекомендую тестувати на речі, які відбуваються в контролері, тобто "should_raise". Що вас хвилює - це результат. Наведені вище тести дозволили мені спробувати різні рішення, і тести залишаються однаковими, незалежно від того, чи рішення викликає виняток, спеціальну візуалізацію тощо.
render :text => 'Not Found', :status => :not_found
.
config.consider_all_requests_local
у вашому environments/development.rb
файлі встановлено значення параметра true . Якщо ви зробите помилку, як описано у прийнятому рішенні, під час постановки / виробництва, ви неодмінно отримаєте 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
вони допоможуть вам ...
Контролер програми
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
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] %>
просто додайте це на сторінку, яку ви хочете зробити на сторінці помилки 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 на значення false, і мій рятувальний_від не запустився. Таким чином я можу перевірити підняття окремо від умовної логіки 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