Як перенаправити на 404 в Rails?


Відповіді:


1049

Не візьміть 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, не знайденої в результаті дії контролера


3
Є причина зробити це самостійно. Якщо ваша програма захоплює всі маршрути з кореня. Це поганий дизайн, але іноді неможливо уникнути.
зміст

7
Цей підхід також дозволяє використовувати пошукові вибухи ActiveRecord (find !, find_by _...! І т. Д.), Які всі створюють виняток ActiveRecord :: RecordNotFound, якщо не знайдено жодної запису (запускає обробник рятувального_відходу).
gjvis

2
Це спричиняє 500 помилок внутрішнього сервера для мене, а не 404. Що я пропускаю?
Гленн

3
Здається, ActionController::RecordNotFoundце кращий варіант?
Пітер Ерліх

4
Код працював великий , але тест не робив , поки я не зрозумів , що я використовував RSpec 2 , який має інший синтаксис: expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)/ через stackoverflow.com/a/1722839/993890
ryanttb

243

Статус HTTP 404

Щоб повернути заголовок 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

ActiveRecord та HTTP 404

Також пам’ятайте, що 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

9
У цьому рішенні є велика проблема; він все одно запустить код у шаблоні. Отже, якщо у вас є проста, спокійна структура і хтось вводить ідентифікатор, який не існує, ваш шаблон буде шукати об'єкт, який не існує.
jcalvert

5
Як було сказано раніше, це не правильна відповідь. Спробуйте Стівена.
Пабло Марамбіо

Змінено обрану відповідь, щоб відобразити кращу практику. Дякую за коментарі, хлопці!
Юваль Кармі

1
Я оновив відповідь ще більше прикладів та примітки про ActiveRecord.
Сімоне Карлетті

1
Версія чуб НЕ зупиняє виконання коду, тому це більш ефективне рішення IMHO.
Gui vieira

60

Щойно обрана відповідь, подана Стівеном Сорокою, є близькою, але не повною. Сам тест приховує той факт, що це не повернення справжнього 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". Що вас хвилює - це результат. Наведені вище тести дозволили мені спробувати різні рішення, і тести залишаються однаковими, незалежно від того, чи рішення викликає виняток, спеціальну візуалізацію тощо.


3
дуже подобається ця відповідь, особливо що стосується тестування вихідних даних, а не методів, викликаних у контролері…
xentek

Rails має вбудований 404 статус: render :text => 'Not Found', :status => :not_found.
Lasse Bunk

1
@JaimeBellmyer - Я впевнений, що він не повертає 200, коли ви знаходитесь у розгорнутому (тобто постановочному / prod) середовищі. Я роблю це в декількох додатках, і це працює, як описано в прийнятому рішенні. Можливо, ви маєте на увазі те, що він повертає значення 200, коли він відтворює екран налагодження в розробці, де, ймовірно, config.consider_all_requests_localу вашому environments/development.rbфайлі встановлено значення параметра true . Якщо ви зробите помилку, як описано у прийнятому рішенні, під час постановки / виробництва, ви неодмінно отримаєте 404, а не 200.
Javid Jamae

18

Ви також можете використовувати файл візуалізації:

render file: "#{Rails.root}/public/404.html", layout: false, status: 404

Де ви можете використовувати макет чи ні.

Іншим варіантом є використання виключень для управління ним:

raise ActiveRecord::RecordNotFound, "Record not found."

13

Вибрана відповідь не працює в 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.


3
це дуже приємне рішення; однак, вам не потрібна || not_foundчастина, просто зателефонуйте find!(зауважте баг), і вона кине ActiveRecord :: RecordNotFound, коли ресурс неможливо отримати. Також додайте ActiveRecord :: RecordNotFound до масиву в умові if.
Marek Příhoda

1
Я б врятував StandardErrorі ні Exception, про всяк випадок. Насправді я залишу стандартну статичну сторінку 500 і взагалі не буду користуватися користувачем render_500, тобто явно rescue_from
масу

7

вони допоможуть вам ...

Контролер програми

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

3
<%= render file: 'public/404', status: 404, formats: [:html] %>

просто додайте це на сторінку, яку ви хочете зробити на сторінці помилки 404, і ви закінчили.


1

Я хотів викинути "звичайний" 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

1
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

0

Щоб перевірити поводження з помилками, ви можете зробити щось подібне:

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

Якщо ви хочете по-різному обробляти 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.