新手问题 关于 Rails 的异常处理

Talon · 2016年12月29日 · 最后由 mengqing 回复于 2017年01月22日 · 4391 次阅读

最近在写 rails 项目的时候,对异常处理这一块有很多困惑,希望各位能帮我解惑,谢谢!下面是问题:

1.怎么处理在 Controller 里面抛出的异常?什么才是处理 Controller 异常的较好的实践?代码如下

class ProductsController < ApplicationController
  def show
    product_id = params[:product_id]
    #如果product_id为空,那么我们是不是应该主动抛出ActiveRecord::RecordNotFound异常,然后交给ApplicationController然后使用rescue_from进行处理?
    raise ActiveRecord::RecordNotFound unless product_id
    #还是应该像下面这样不主动检测product_id,让程序自己在查找对应的product时抛出异常?
    @product = Product.find(params[:product_id])

    #如果交由ApplicationControlller统一处理同类型的异常,那么就意味着所有抛出此类型异常的代码之后的行为都是相同的(如添加错误信息,跳转)
    #那么如果我有些可能抛出此类型异常的地方我不想跟其他地方一样抛出异常之后产生同样的行为,那么我应该怎么办?在Action中用begin-rescue-end块吗?这样会不会显得Action中的代码过于臃肿?
  end
end

2.我们应不应该在 Model 中的方法内抛出或者处理异常?

class Product < ApplicationRecord
  def title
    self.name + ' ' + product_properties.map(&:value).join(' ')
  end
  #像上面这段代码,如果product没有name这个字段也没有name这个方法,那么会抛出一个NoMethodError异常,我们应该怎样处理它?
  #产品发布到生产环境,怎么处理这些有可能在controller和model里面发生的异常才有更好的用户体验?
end

问题 1:

Controller 里面,如果是调用 find 方法,你不需要额外处理 NotFound 的异常,因为 Rails 已经做了

def show
  @product = Product.find(params[:product_id])
end

上面这个例子,如果没找到,将会抛出 ActiveRecord::RecordNotFound,同时生产环境将会以 404 页面返回。

问题 2:

Model 里面是否要抛出异常,这个要看什么情况了,你要需要,也是可以那么做的(就好比 find 也是直接抛异常的),但总点是,Controller 里面要拦截处理,不要以 500 页面返回。

#1 楼 @huacnlee 谢谢!我看之前的一个帖子里你说 find 方法抛出异常只有在 Show 这个 Action 里面不需要手动处理,生产环境会自动 render 404,那 Controller 里面其他的 Action 呢?如果其他的 Action 需要手动捕获的话在 ApplicationController 里面 rescue_from 这种方式可好?如果在 rescue_from 里面指定了与 render 404 不同的行为,那么在 Show Action 中 find 方法抛出异常之后会怎样?render 404 or 自定义行为?

#1 楼 @huacnlee 我去看一下 Ruby-China 的源码吧

@Talon 你的这个 我是这么认为的

before_action :find_project_by_project_id, :only => [:new, :create, :show, :edit, :destroy, :update]
  before_action :find_optional_project, :only => [:index, :edit]
def show
    @product = Product.find(params[:product_id])
  end

应该是不需要手动捕获的。

问题 1

There is no need to explicitly check whether the id exists or not. If you setup the routes correctly, then it would never have passed the routes if the required key does not exist.

# routes.rb

# products GET /products/:id(.:format) products#show
resources :products

In terms of exception rescuing on the controller level, like @huacnlee have said, you don't need to handle ActiveRecord::RecordNotFound as Rails has already handled it for you (rendering / returning 404), and you don't need to apply rescue_from in ApplicationController. If however you need to handle it differently in any particular actions, you can always rescue it per action.

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    redirect_to :root, flash: { error: "产品不存在" }
  end
end

问题 2

You can rescue exceptions in model, just like any of the ruby classes and methods. However, general practices is, you'll only ever need to rescue exceptions if you don't know whether it is going to raise exceptions or not (这不是废话嘛..), eg: user inputs, external sources, api responses, and even then it should be handled on a higher level, eg: controllers, service objects, form objects and validators etc.

Issues like you've described, (NoMethodError) should have never made to production code base with unit testings. Thats why it is essential to have unit testing on each of the methods in your model.

需要 登录 后方可回复, 如果你还没有账号请 注册新账号