回答:
自分で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の詳細情報を参照してください
ActionController::RecordNotFound
良いオプションですか?
expect { visit '/something/you/want/to/404' }.to raise_error(ActionController::RoutingError)
/ via stackoverflow.com/a/1722839/993890
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
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
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」をテストすることはお勧めしません。気になるのは出力です。上記のテストでは、さまざまなソリューションを試すことができました。ソリューションが例外や特別なレンダリングなどを発生させても、テストは同じです。
render :text => 'Not Found', :status => :not_found
。
config.consider_all_requests_local
パラメーターがtrueに設定されている開発画面でデバッグ画面をレンダリングすると200を返すことenvironments/development.rb
です。あなたはエラーが発生した場合は、受け入れられた解決策で説明したように、ステージング/生産で、あなたは間違いなく404ではなく、200取得します
エラーハンドラーがミドルウェアに移動されたため、選択した回答は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に異なるビューがあります。
|| not_found
部分は必要ありません。find!
(強打に注意してください)を呼び出すだけで、リソースを取得できないときにActiveRecord :: RecordNotFoundがスローされます。また、ActiveRecord :: RecordNotFoundをif条件の配列に追加します。
StandardError
ませんException
。実際には、標準の500静的ページをそのままにしrender_500
、カスタムをまったく使用しません。つまりrescue_from
、404に関連する一連のエラーを明示的に指定します
これらはあなたを助けるでしょう...
アプリケーションコントローラー
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
管理者ではないログインしたユーザーには「通常の」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に基本ロジックを配置しましたが、より具体的なコントローラーに配置して、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