Rails 请求格式为 json 时,ActiveRecord::Transtration 不能捕捉错误

darren · September 30, 2019 · Last by darren replied at September 30, 2019 · 3164 hits

当请求格式为 json 格式时,错误不能捕捉 view 层

= semantic_form_for @order, url: orders_path(format: :json)  do |of|

设立使用了 json 请求格式

controller 层

def create
    @order = Order.new(params.require(:order).permit!)
    begin
        ActiveRecord::Base.transaction do
            @order.save!
        end
        redirect_to order_path(@order)
    rescue ActiveRecord::RecordInvalid
        render :new
    rescue StandardError=>e
        flash[:notice] = e
        render :new
    end
end

如果是正常的路由

= semantic_form_for @order, url: orders_path  do |of|

可以正常返回界面 为什么请求的格式和对 begin 和 rescue 的错误捕捉有影响?

你确定是 save! 报错?不是 new 对象的时候报错? 你是想 ajax 异步的话可以用 remote: true 你可以打个断点看看是不是@order = Order.new(params.require(:order).permit!) 这步炸了

new 对象不会报错的,而且 ActiveRecord::InvalidError 这个是验证错误,save! 这个方法跑出异常的 (因为 ActiveRecord 只能抛出异常触发回滚),和异步请求无关,现在问题是,不同的请求格式竟然引起可能导致 rescue 不执行!!!??? 这是框架的原因,排查很久不知道为什么请求格式会引起 rescue 不起作用

继续调试跟进 在@order.save之前输出 caller_locations (因为最近的错误就是@order.save!,往后就是报错,拿不到 caller_locations)

def create
    @order = Order.new(params.require(:order).permit!)
    begin
        ActiveRecord::Base.transaction do
            puts caller_locations
            @order.save!
        end
        redirect_to order_path(@order)
    rescue ActiveRecord::RecordInvalid
        render :new
    rescue StandardError=>e
        flash[:notice] = e
        render :new
    end
end

发现这两种方法请求在报错前,调用完全一样,那么可能是调用过程某些参数不一样,沿着调用方法一层层对比参数试一下

首先应该明确返回什么 json,什么 http status

比如

{ saved: false,  error_message: 'some message' } # 失败的时候
{ saved: sucess, id: xxx } # 成功的时候

比如可以类似这样,也可以使用 jbuilder

def create
  @order = Order.new(params.require(:order).permit!)

  if @order.save
    render json: success_json
  else
    render json: fail_json, status: :bad_request
  end
end

通过一级一级调用往上找,终于找到原因了, /home/linqining/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/actionpack-5.2.2.1/lib/action_controller/metal/rescue.rb:22:in `process_action'

       21: def process_action(*args)
   22:   super
   23: rescue Exception => exception
=> 24:   binding.pry
   25:   request.env["action_dispatch.show_detailed_exceptions"] ||= show_detailed_exceptions?
   26:   rescue_with_handler(exception) || raise
   27: end

原因是在 rescue, binding 显示的事 missing template,controller 中的方法的确没有 render json,导致没有 missing template, 而后面的错误处理 rescue_with_handler 把 activerecord 的错误一并处理,并且先显示 activecord 的错误 (这里不清楚 rescue 的错误优先顺序,知悉的大神告诉一下),我猜测 rescue 显示错误的时候是根据上下文出现的顺序显示的,显示 activecord record 引起的错误,然后是 missing template 引起错误,

def create
    @order = Order.new(params.require(:order).permit!)
    begin
        ActiveRecord::Base.transaction do
            puts caller_locations
            @order.save!
        end
        respond_to do |format|
        format.html do
             redirect_to order_path(@order)
        end
        format.json do
                    render json: {status:200, message: ''}
        end
    end

    rescue ActiveRecord::RecordInvalid
                    respond_to do |format|
                format.html do
                    render :new
                end
                format.json do
                    render json: {status:200, message: ''}
                end
            end
    rescue StandardError=>e
        flash[:notice] = e
                respond_to do |format|
                format.html do
                    render :new
                end
                format.json do
                    render json: {status:200, message: ''}
                end
            end
    end
end

根据请求的类型,返回相应的数据就解决问题了

思考

这次的疑惑是 rescue 的处理顺序带来的,既然 activerecord 已经捕捉错误了,后面显示错误的时候,是否应该只应显示 render template missing 的错误?至少有个地方可以改进 1.能否根据请求的格式自动选择相应的格式,这样即使是出现错误,没有指明返回收据类型,返回 204 no_content 更符合实际,而不是返回 500

Reply to heroyct

对的就是这个错误,但是后面 rescue 的时候,把 activerecord 的错误抛给我了,实际上应该是 missing template 的,但是我觉得既然我 rescue 了 activerecord::invalid,后面就不应该显示这个错误,只显示 missingtemplate 就好了,这个不影响功能,但是可以优化

Reply to darren

我发现是我理解错了,原来是 rescue ActiveRecord::RecordInvalid 的时候,render 的时候又抛出错误了,然后新的错误就在错误的数组上新增,这个是没问题的,是我对 rescue 的机制认识不够深入

You need to Sign in before reply, if you don't have an account, please Sign up first.