Rails에서 404로 리디렉션하는 방법은 무엇입니까?


482

Rails에서 404 페이지를 '가짜'로 만들고 싶습니다. PHP에서는 오류 코드가있는 헤더를 다음과 같이 보냅니다.

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

Rails는 어떻게 되나요?

답변:


1049

404를 직접 렌더링하지 마십시오. 이유가 없습니다. Rails에는이 기능이 이미 내장되어 있습니다. 404 페이지를 표시하려면 다음 render_404not_found같이 메소드를 작성하십시오 (또는 호출 한대로) ApplicationController.

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

또한 핸들 레일 AbstractController::ActionNotFoundActiveRecord::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)

그리고 가장 작은 :

assert_raises(ActionController::RoutingError) do 
  get '/something/you/want/to/404'
end

또는 Rails render 404의 추가 정보를 컨트롤러 작업에서 찾을 수 없습니다.


3
직접해야 할 이유가 있습니다. 애플리케이션이 루트에서 모든 경로를 가로채는 경우 나쁜 디자인이지만 때로는 피할 수없는 경우도 있습니다.
ablemike

7
이 방법을 사용하면 레코드를 찾을 수없는 경우 (rescue_from 핸들러를 트리거하는) 모두 ActiveRecord :: RecordNotFound 예외를 발생시키는 ActiveRecord bang 파인더 (find !, find_by _...! 등)를 사용할 수 있습니다.
gjvis 2016 년

2
이로 인해 404가 아니라 500 내부 서버 오류가 발생합니다. 무엇을 놓치고 있습니까?
Glenn

3
ActionController::RecordNotFound더 나은 옵션 인 것 같습니다 .
피터 Ehrlich

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 헤더를 반환하려면 :statusrender 메소드 의 옵션을 사용하십시오 .

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::RecordNotFound는 404 오류 페이지 표시 와 같은 일부 ActiveRecord 오류를 복구합니다 .

그것은 당신이이 행동을 스스로 구출 할 필요가 없다는 것을 의미합니다

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
이 솔루션에는 큰 문제가 있습니다. 여전히 템플릿에서 코드를 실행합니다. 따라서 단순하고 편안한 구조를 가지고 있고 누군가 존재하지 않는 ID를 입력하면 템플릿이 존재하지 않는 개체를 찾습니다.
jcalvert

5
앞에서 언급했듯이 이것은 정답이 아닙니다. 스티븐스를보십시오.
Pablo Marambio

더 나은 연습을 반영하기 위해 선택한 답변을 변경했습니다. 의견 주셔서 감사합니다, 여러분!
유발 카르미

1
더 많은 예제와 ActiveRecord에 대한 메모로 답변을 업데이트했습니다.
Simone Carletti

1
뱅 버전은 코드 실행을 중지하므로 더 효과적인 솔루션 IMHO입니다.
Gui vieira

60

Steven Soroka가 제출 한 새로 선택된 답변은 가깝지만 완료되지 않았습니다. 테스트 자체는 이것이 실제 404를 반환하지 않는다는 사실을 숨 깁니다. 200- "성공"상태를 반환합니다. 원래의 대답은 더 가까웠지만 실패가없는 것처럼 레이아웃을 렌더링하려고 시도했습니다. 이것은 모든 것을 고친다 :

render :text => 'Not Found', :status => '404'

다음은 RSpec 및 Shoulda 매처를 사용하여 404를 반환 할 것으로 예상되는 일반적인 테스트 세트입니다.

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")에 대한 테스트는 권장하지 않습니다. 관심있는 것은 출력입니다. 위의 테스트를 통해 다양한 솔루션을 시도 할 수 있었고 솔루션이 예외, 특수 렌더링 등을 발생시키는 지 여부와 상관없이 테스트는 동일하게 유지됩니다.


3
컨트롤러에서 호출되는 메소드가 아니라 출력 테스트와 관련하여이 답변을 정말 좋아합니다.
xentek

Rails의 내장 상태는 404 render :text => 'Not Found', :status => :not_found입니다.
Lasse Bunk

1
@JaimeBellmyer- 배포 된 (예 : 준비 / 제품) 환경에있을 때 200을 반환 하지 않을 것이라고 확신 합니다. 여러 응용 프로그램 에서이 작업을 수행하며 허용되는 솔루션에 설명 된대로 작동합니다. 아마도 당신이 말하는 것은 아마도 파일 config.consider_all_requests_local에서 매개 변수가 true로 설정되어 있는 개발중인 디버그 화면을 렌더링 할 때 200을 반환한다는 것 environments/development.rb입니다. 승인 된 솔루션에 설명 된대로 준비 / 생산에서 오류가 발생하면 200이 아니라 404를 얻게됩니다.
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 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

이것은 확실히 개선 될 수 있지만 적어도 핵심 Rails 함수를 재정의하지 않고 not_found 및 internal_error에 대한 다른 견해를 가지고 있습니다.


3
이것은 매우 좋은 해결책입니다. 그러나 || not_found부분 이 필요하지 않고 전화 find!(뱅뱅을 통지)하면 리소스를 검색 할 수 없을 때 ActiveRecord :: RecordNotFound가 발생합니다. 또한 if 조건에서 ActiveRecord :: RecordNotFound를 배열에 추가하십시오.
Marek Příhoda

1
만일을 대비해서, 나는 구출 StandardError하지 않을 것이다 Exception. 사실은 내가 표준 500 정적 페이지를 떠나 사용자 정의를 사용하지 것이다 render_500의미 모두에서 거 야 명시 적으로 rescue_from404 관련된 오류의 배열
Dr.Strangelove

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 / errors / 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에 배치했지만 더 구체적인 컨트롤러에 배치하여 하나의 컨트롤러에 대해서만 특수 로직을 가질 수 있습니다.

ENV [ 'RESCUE_404']와 함께 if를 사용하는 이유는 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.