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

434

Я хотел бы "подделать" страницу 404 в Rails. В PHP я бы просто послал заголовок с кодом ошибки как таковой:

header("HTTP/1.0 404 Not Found");

Как это делается с Rails?

Теги:
http
http-status-code-404

9 ответов

1008
Лучший ответ

Не делайте 404 себя, нет причин; У Rails уже встроена эта функциональность. Если вы хотите показать страницу 404, создайте метод render_404 (или not_found, как я его называл), в ApplicationController следующим образом:

def not_found
  raise ActionController::RoutingError.new('Not Found')
end

Rails также обрабатывает AbstractController::ActionNotFound и ActiveRecord::RecordNotFound таким же образом.

Это улучшает две вещи:

1) Он использует встроенный обработчик Rails в rescue_from для отображения страницы 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)

И minitest:

assert_raises(ActionController::RoutingError) do 
  get '/something/you/want/to/404'
end
  • 0
    В тесте / модуле я полагаю, что это что-то вроде: assert_raises (ActionController :: RoutingError) действительно получает конец '/ кое-что / вы / хотите / до / 404'
  • 3
    Есть причина сделать это самостоятельно. Если ваше приложение захватывает все маршруты из корня. Это плохой дизайн, но иногда его невозможно избежать.
Показать ещё 20 комментариев
228

Статус 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

Если вы хотите, чтобы действие отображало страницу с ошибкой и останавливалось, просто используйте оператор return.

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 check. Просто используйте версию взлома.

def show
  user = User.find_by_email!(params[:email])
  # ...
end
  • 7
    Есть большая проблема с этим решением; он все равно будет запускать код в шаблоне. Поэтому, если у вас простая, спокойная структура, и кто-то вводит идентификатор, который не существует, ваш шаблон будет искать объект, который не существует.
  • 5
    Как упоминалось ранее, это не правильный ответ. Попробуйте Стивена.
Показать ещё 8 комментариев
56

Недавно выбранный ответ, представленный Стивеном Сорокой, близок, но не завершен. Сам тест скрывает тот факт, что это не возвращает истинный 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
    Мне очень нравится этот ответ, особенно в отношении тестирования выходных данных, а не методов, вызываемых в контроллере…
  • 0
    Rails имеет встроенный статус 404: render :text => 'Not Found', :status => :not_found .
Показать ещё 1 комментарий
9

Выбранный ответ не работает в Rails 3.1+, поскольку обработчик ошибок был перемещен в промежуточное программное обеспечение (см. github issue).

Вот решение, которое я нашел, которым я очень доволен.

В 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.
  • 1
    Я бы спасал StandardError а не Exception , на всякий случай. На самом деле я оставлю стандартную статическую страницу 500 и вообще не буду использовать custom render_500 , то есть я буду явно rescue_from массив ошибок, связанных с 404
8

Вы также можете использовать файл рендеринга:

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

Где вы можете использовать макет или нет.

Другой вариант - использовать Исключения для его управления:

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

это поможет вам...

Контроллер приложений

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
0
<%= render file: 'public/404', status: 404, formats: [:html] %>

просто добавьте это на страницу, которую вы хотите отобразить на странице ошибки 404, и все готово.

0

Если вы хотите обрабатывать разные 404s по-разному, подумайте о том, чтобы поймать их в своих контроллерах. Это позволит вам делать такие вещи, как отслеживание количества 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
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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню