Railsで404にリダイレクトする方法は?


回答:


1049

自分で404をレンダリングしないでください。理由はありません。Railsにはすでにこの機能が組み込まれています。404ページを表示したい場合は、次のようにrender_404メソッドを(またはnot_found私が呼び出したように)作成しますApplicationController

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

RailsのもハンドルAbstractController::ActionNotFound、そしてActiveRecord::RecordNotFound同じように。

これは次の2つのことを行います。

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レンダー404の詳細情報を参照してください


3
自分でやるには理由があります。アプリケーションがルートからすべてのルートをハイジャックする場合。これは悪いデザインですが、避けられないこともあります。
2011年

7
この方法では、レコードが見つからない場合(rescue_fromハンドラーをトリガーする)ActiveRecord :: RecordNotFound例外をすべて発生させるActiveRecord bang finder(find!、find_by _...!など)も使用できます。
gjvis

2
これにより、404ではなく500内部サーバーエラーが発生します。何が欠けていますか?
Glenn

3
以下のように思えるActionController::RecordNotFound良いオプションですか?
Peter Ehrlich、2013

4
コードは適切に機能しましたが、構文が異なるRSpec 2を使用していることに気づくまで、テストは機能しませんでした:expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)/ via stackoverflow.com/a/1722839/993890
ryanttb

243

HTTP 404ステータス

404ヘッダーを返すには:status、renderメソッドのオプションを使用します。

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に委任することで、これを簡略化できます。単にbangバージョンを使用してください。

def show
  user = User.find_by_email!(params[:email])
  # ...
end

9
このソリューションには大きな問題があります。テンプレート内のコードは引き続き実行されます。したがって、単純で落ち着いた構造であり、誰かが存在しないIDを入力した場合、テンプレートは存在しないオブジェクトを探します。
jcalvert

5
前述のように、これは正しい答えではありません。スティーブンを試してください。
Pablo Marambio

より良い実践を反映するように選択された回答を変更しました。みんなコメントありがとう!
Yuval Karmi、2011

1
回答を更新して、より多くの例とActiveRecordに関するメモを追加しました。
Simone Carletti、2011年

1
bangバージョンはコードの実行を停止するため、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です。あなたはエラーが発生した場合は、受け入れられた解決策で説明したように、ステージング/生産で、あなたは間違いなく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

これは確かに改善される可能性がありますが、少なくとも、コアのRails関数をオーバーライドせずに、not_foundとinternal_errorに異なるビューがあります。


3
これは非常に優れたソリューションです。ただし、この|| not_found部分は必要ありません。find!(強打に注意してください)を呼び出すだけで、リソースを取得できないときにActiveRecord :: RecordNotFoundがスローされます。また、ActiveRecord :: RecordNotFoundをif条件の配列に追加します。
MarekPříhoda14年

1
万が一に備えて、私は救いStandardErrorませんException。実際には、標準の500静的ページをそのままにしrender_500、カスタムをまったく使用しません。つまりrescue_from、404に関連する一連のエラーを明示的に指定します
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に基本ロジックを配置しましたが、より具体的なコントローラーに配置して、1つのコントローラーにのみ特別なロジックを持たせることもできます。

ENV ['RESCUE_404']でifを使用している理由は、AR :: RecordNotFoundの発生を単独でテストできるようにするためです。テストでは、このENV変数を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.