如何在Rails中redirect到404?
我想在Rails中“伪造”一个404页面。 在PHP中,我只需要发送一个包含错误代码的头文件:
header("HTTP/1.0 404 Not Found");
Rails是如何完成的?
不要自己渲染404,没有理由; Rails已经内置了这个function。 如果你想显示一个404页面,在ApplicationController
像这样创build一个render_404
方法(或者我称之为not_found
):
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!
而不必写丑陋的条件语句。
作为奖励,在testing中处理也非常容易。 例如,在rspec集成testing中:
# 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
HTTP 404状态
要返回404头,只需使用render方法的: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错误,比如显示404错误页面的ActiveRecord::RecordNotFound
。
这意味着你不需要自己拯救这个行动
def show user = User.find(params[:id]) end
当用户不存在时, User.find
引发一个ActiveRecord::RecordNotFound
。 这是一个非常强大的function。 看下面的代码
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
Steven Soroka提交的新select的答案接近,但并不完整。 testing本身隐藏了这样一个事实:这不是返回一个真正的404 – 它返回一个200的状态 – “成功”。 原来的答案更接近,但试图渲染布局,就好像没有发生故障。 这解决了一切:
render :text => 'Not Found', :status => '404'
下面是我使用RSpec和Shoulda匹配器返回404的典型testing集:
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
这种健康的偏执让我发现内容types不匹配时,一切都看起来桃色:)我检查所有这些元素:分配的variables,响应代码,响应内容types,模板呈现,布局呈现,闪光消息。
我会跳过严格的HTML应用程序的内容types检查…有时。 毕竟,“怀疑论者检查所有的抽屉”:)
http://dilbert.com/strips/comic/1998-01-20/
仅供参考:我不推荐testing控制器中正在发生的事情,即“should_raise”。 你关心的是输出。 我上面的testing允许我尝试各种解决scheme,testing保持不变,无论解决scheme是否引发exception,特殊渲染等
所选的答案在Rails 3.1+中不起作用,因为error handling程序被移到了中间件(参见github问题 )。
这是我find的解决scheme,我很满意。
在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
这当然可以改善,但至less,我有not_found和internal_error不重写核心Rails函数不同的意见。
你也可以使用渲染文件:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
哪里可以select使用布局。
另一种select是使用例外来控制它:
raise ActiveRecord::RecordNotFound, "Record not found."
这些将帮助你…
应用控制器
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
为了testingerror handling,你可以这样做:
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 / Btesting等。
我在这里将基础逻辑放在ApplicationController中,但是它也可以放在更具体的控制器中,只有一个控制器才有特殊的逻辑。
我使用ENV ['RESCUE_404']的原因是,我可以单独testingAR :: RecordNotFound的提升。 在testing中,我可以将这个ENV var设置为false,并且我的rescue_from不会触发。 这样我就可以testing独立于条件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
<%= render file: 'public/404', status: 404, formats: [:html] %>
只需将此添加到您要呈现到404错误页面的页面即可。