如何在Rails中重定向到404?

Yuv*_*rmi 474 ruby ruby-on-rails http http-status-code-404

我想在Rails中"伪造"一个404页面.在PHP中,我只是发送带有错误代码的标头:

header("HTTP/1.0 404 Not Found");
Run Code Online (Sandbox Code Playgroud)

如何用Rails完成?

Ste*_*oka 1038

不要自己渲染404,没有理由; Rails已经内置了这个功能.如果要显示404页面,请创建一个render_404方法(或not_found我称之为),ApplicationController如下所示:

def not_found
  raise ActionController::RoutingError.new('Not Found')
end
Run Code Online (Sandbox Code Playgroud)

Rails的也处理AbstractController::ActionNotFound,并ActiveRecord::RecordNotFound以同样的方式.

这样可以做得更好:

1)它使用Rails的内置rescue_from处理程序来呈现404页面,2)它会中断代码的执行,让你做一些好的事情,比如:

  user = User.find_by_email(params[:email]) or not_found
  user.do_something!
Run Code Online (Sandbox Code Playgroud)

无需编写丑陋的条件语句.

作为奖励,它在测试中也非常容易处理.例如,在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)
Run Code Online (Sandbox Code Playgroud)

最小的:

assert_raises(ActionController::RoutingError) do 
  get '/something/you/want/to/404'
end
Run Code Online (Sandbox Code Playgroud)

或者从控制器操作中找不到Rails渲染404中的更多信息

  • 这种方法还允许你使用ActiveRecord bang finders(find!,find_by _...!等),如果没有找到记录,它们都会引发ActiveRecord :: RecordNotFound异常(触发rescue_from处理程序). (7认同)
  • 代码工作得很好但测试没有,直到我意识到我使用的RSpec 2有不同的语法:`expect {visit'/ something/you/want/to/404'} .to raise_error(ActionController :: RoutingError)` /通过http://stackoverflow.com/a/1722839/993890 (4认同)
  • 有理由自己做.如果您的应用程序劫持了根目录中的所有路由.这是糟糕的设计,但有时是不可避免的. (3认同)
  • 好像`ActionController :: RecordNotFound`是更好的选择吗? (3认同)
  • 这为我提出了500内部服务器错误,而不是404.我错过了什么? (2认同)
  • 假设这个答案是Railsy的方式:我讨厌Rails如何不能坚持一个范例:所以你提出异常来获得404,但是你坚持渲染:其他一切的状态,比如401或403?这没有意义. (2认同)
  • 如果你只是想模仿默认行为```raise ActiveRecord :: RecordNotFound``` (2认同)
  • 这仍然是真的吗?至少在开发过程中,当我的控制器抛出ActiveRecord :: RecordNotFound时,我得到一个错误页面. (2认同)
  • 现在最好使用fail:`fail ActiveRecord :: RecordNotFound,'Not Found'` (2认同)

Sim*_*tti 241

HTTP 404状态

要返回404标头,只需使用:statusrender方法的选项.

def action
  # here the code

  render :status => 404
end
Run Code Online (Sandbox Code Playgroud)

如果要渲染标准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
Run Code Online (Sandbox Code Playgroud)

并在你的行动中称呼它

def action
  # here the code

  render_404
end
Run Code Online (Sandbox Code Playgroud)

如果您希望操作呈现错误页面并停止,只需使用return语句.

def action
  render_404 and return if params[:something].blank?

  # here the code that will never be executed
end
Run Code Online (Sandbox Code Playgroud)

ActiveRecord和HTTP 404

还记得Rails拯救了一些ActiveRecord错误,例如ActiveRecord::RecordNotFound显示404错误页面.

这意味着您不需要自己拯救此行为

def show
  user = User.find(params[:id])
end
Run Code Online (Sandbox Code Playgroud)

User.findActiveRecord::RecordNotFound当用户不存在时引发.这是一个非常强大的功能.请查看以下代码

def show
  user = User.find_by_email(params[:email]) or raise("not found")
  # ...
end
Run Code Online (Sandbox Code Playgroud)

您可以通过委托Rails检查来简化它.只需使用爆炸版.

def show
  user = User.find_by_email!(params[:email])
  # ...
end
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案存在很大问题; 它仍然会在模板中运行代码.因此,如果您有一个简单,安静的结构,并且有人输入了一个不存在的ID,那么您的模板将会查找不存在的对象. (7认同)
  • 如前所述,这不是正确的答案.试试史蒂文的. (5认同)

Jai*_*yer 59

Steven Soroka提交的新选择答案很接近,但并不完整.测试本身隐藏了这样一个事实,即这不会返回真正的404 - 它将返回200的状态 - "成功".原始答案更接近,但尝试渲染布局,好像没有发生故障.这解决了一切:

render :text => 'Not Found', :status => '404'
Run Code Online (Sandbox Code Playgroud)

这是我使用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
Run Code Online (Sandbox Code Playgroud)

这个健康的偏执让我发现内容类型不匹配,当其他一切看起来很好:)我检查所有这些元素:分配变量,响应代码,响应内容类型,模板渲染,布局呈现,Flash消息.

我将跳过内容类型检查严格html的应用程序...有时.毕竟,"怀疑论者检查所有抽屉":)

http://dilbert.com/strips/comic/1998-01-20/

仅供参考:我不建议测试控制器中发生的事情,即"should_raise".你关心的是输出.我上面的测试允许我尝试各种解决方案,无论解决方案是异常,特殊渲染等,测试都保持不变.

  • 真的很喜欢这个答案,特别是关于输出的测试,而不是控制器中调用的方法...... (3认同)

Pau*_*lgo 18

您还可以使用渲染文件:

render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Run Code Online (Sandbox Code Playgroud)

在哪里可以选择使用布局.

另一种选择是使用Exceptions来控制它:

raise ActiveRecord::RecordNotFound, "Record not found."
Run Code Online (Sandbox Code Playgroud)


Aug*_*ger 12

所选答案在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
Run Code Online (Sandbox Code Playgroud)

并在application.rb:

config.after_initialize do |app|
  app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
Run Code Online (Sandbox Code Playgroud)

在我的资源(显示,编辑,更新,删除)中:

@resource = Resource.find(params[:id]) or not_found
Run Code Online (Sandbox Code Playgroud)

这当然可以改进,但至少,我对not_found和internal_error有不同的看法,而没有覆盖核心Rails函数.

  • 这是一个非常好的解决方案; 但是,你不需要`|| not_found`部分,只需调用`find!`(注意爆炸),当无法检索资源时,它将抛出ActiveRecord :: RecordNotFound.此外,在if条件中将ActiveRecord :: RecordNotFound添加到数组中. (3认同)

Can*_*mak 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
Run Code Online (Sandbox Code Playgroud)

错误控制器

class ErrorsController < ApplicationController
  def error_404
    @not_found_path = params[:not_found]
  end
end
Run Code Online (Sandbox Code Playgroud)

意见/错误/ 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
Run Code Online (Sandbox Code Playgroud)