我想在Rails中“伪造”一个404页面。在PHP中,我只会发送一个带有错误代码的报头:

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

Rails如何做到这一点?


当前回答

如果希望以不同的方式处理不同的404,可以考虑在控制器中捕获它们。这将允许你做一些事情,如跟踪不同用户组生成的404数量,让支持人员与用户交互,找出哪里出了问题/用户体验的哪一部分可能需要调整,进行A/B测试等。

我在这里将基本逻辑放在ApplicationController中,但它也可以放在更特定的控制器中,以便只针对一个控制器拥有特殊的逻辑。

我使用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

其他回答

如果希望以不同的方式处理不同的404,可以考虑在控制器中捕获它们。这将允许你做一些事情,如跟踪不同用户组生成的404数量,让支持人员与用户交互,找出哪里出了问题/用户体验的哪一部分可能需要调整,进行A/B测试等。

我在这里将基本逻辑放在ApplicationController中,但它也可以放在更特定的控制器中,以便只针对一个控制器拥有特殊的逻辑。

我使用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

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错误,例如ActiveRecord::RecordNotFound显示404错误页面。

这意味着您不需要自己拯救这个操作

def show
  user = User.find(params[:id])
end

用户。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
<%= render file: 'public/404', status: 404, formats: [:html] %>

只需将此添加到要呈现到404错误页面的页面,就完成了。

所选的答案在Rails 3.1+中不工作,因为错误处理程序被移动到中间件(见github问题)。

这是我找到的解决方案,我很满意。

控制器:

  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

这当然可以改进,但至少,我对not_found和internal_error有不同的视图,而不重写核心Rails函数。

为了测试错误处理,你可以这样做:

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