当ID未找到时,在RoR中进行通用救援控制器

17
我遇到了这样一种情况:我的应用程序在数据库中查找一个不存在的id。抛出了一个异常。当然,对于任何Web开发人员来说,这都是一个非常普遍的情况。
感谢这个答案,我知道使用 rescue 可以很好地处理这种情况,像这样:
def show
  @customer = Customer.find(params[:id])
  rescue ActiveRecord::RecordNotFound #customer with that id cannot be found
    redirect_to action: :index        #redirect to index page takes place instead of crashing
end

如果找不到客户,用户将被重定向到索引页面。这个功能完全正常。

现在,这很好,但是我需要在像show、edit、destroy等操作中进行相同的救援尝试,即每个需要特定id的控制器方法。

话虽如此,我的问题是: 难道没有一种通用的方法可以告诉我的控制器,如果它无法在任何方法中找到id,它将重定向到索引页面(或者通常执行特定的任务)吗?

4个回答

37

你必须使用rescue_from来完成此任务。请参见Action Controller概述指南中的示例。

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

  private

  def record_not_found
    redirect_to action: :index
  end
end

1
太棒了,这就解决了问题,谢谢!顺便问一下:为什么那个方法应该是私有的? - Dennis Hackethal
1
仅为了清洁度:该方法不需要对其他控制器可见,因此它被声明为私有。如果该方法未被声明为私有,它同样可以工作。 - Baldrick
2
你也可以添加一个闪现消息 flash[:notice] = "未找到记录" - Andreas Lyngstad
1
非常好,真的帮了我很多。 - Ravindra

8
Rails内置了一个名为rescue_from的类方法,用于处理异常。
class CustomersController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :index
  ...
end

这看起来非常优雅。我尝试了一下,但在浏览器中它只留下了一个空白页面,在URL中仍然显示“customers/:id”。我错过了什么吗?在我看来,它似乎试图呈现索引而不是重定向到它。 - Dennis Hackethal
你是对的。尝试像 Baldrick 下面建议的那样做(即使用重定向的中间方法)。 - Zach Kemp

3
如果您是在单个控制器中执行此操作(而不是在每个控制器中全局执行此操作),那么有几个选项:
您可以使用before_filter来设置资源:
class CustomerController < ApplicationController
  before_filter :get_customer, :only => [ :show, :update, :delete ]

  def show
  end

  private

  def get_customer
    @customer = ActiveRecord.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      redirect_to :action => :index
  end
end

或者您可以使用一种方法。最近我一直在沿用这种方法,而不是在视图中使用实例变量,它也可以帮助您解决问题:

class CustomerController < ApplicationController
  def show
    # Uses customer instead of @customer
  end

  private

  def customer
    @customer ||= Customer.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      redirect_to :action => :index
  end
  helper_method :customer
end

“全局”这个词引起了我的注意。有没有一种方法可以在所有控制器中实现这个功能?那将非常有帮助。 - Dennis Hackethal
1
我认为以全局方式执行我所描述的操作可能需要一些元编程。但是,要简单地在全局范围内拯救ActiveRecord :: RecordNotFound,您可以在您的ApplicationController中使用rescue_from。 - bratsche

1
在某些情况下,我会建议您使用Model.find_by_id(id)而不是Model.find(id)。find_by_id 返回 nil ,而不是抛出异常,如果找不到记录。
请确保检查nil以避免 NoMethodError
附言:值得一提的是, Model.find_by_id(id)在功能上等同于 Model.where(id:id),这将允许您构建一些其他关系(如果您想要)。

我知道你的意思是wherefind_by在某种程度上相似,因为它们都不会在找不到记录时抛出异常,但值得注意的是,where返回一个ActiveRecord关系,而find_by返回一条单独的记录(如果不是nil的话)。此外,我认为与其捕获异常并在各处防止空指针异常,我更愿意抛出异常。不确定这样做的价值在哪里。 - Dennis Hackethal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接