1

假设我们有以下场景:

class MyModel < ActiveRecord::Base
  after_save :throw_after_save
  after_commit :throw_after_commit

  private
    def throw_after_save
      raise "raising on after_save"
    end

    def throw_after_commit
      raise "raising on after_commit"
    end
end

class MyController < ApplicationController
  def callback
    begin
      MyModel.new(params).save
    rescue
      flash[:alert] = "Failed persisting to external system. Try again."
      Airbrake.notify(
        error_class: "External System Persistence",
        error_message: "External System Persistence: Failed to persist data",
        parameters: params
      )
    end

    redirect_to root_path
  end
end

我们从外部系统获得回调(用户填写一些数据并为帐户创建一组临时属性)。

让我们假设我们希望在对我们进行回调之后在本地保存一些数据。在这个数据被持久化之后,我们想要调用外部系统来完成账户创建过程。外部系统将返回一个结果,通知我们成功,其中包含一些我们需要在本地持久保存的额外数据。我们还知道,在某些特殊情况下,远程系统上的持久性不会成功(假设系统不可用,或者他们的一端出了问题)。

目标是捕获外部持久性异常以及成功并采取相应措施。在成功的情况下,一切都是笨拙的:额外的数据存储在本地,redirect_to root_path发生了。然而,在异常的情况下,我们希望向用户表明这一点(也许设置 aflash[:alert]显示在视图中)。

我们曾尝试使用ActiveRecord::Callbacks从模型中抛出异常,after_saveafter_commit通过设置警报并可能将异常传递给某些异常通知系统(如 Airbrake)在控制器处处理该异常。在 的情况下after_save,模型抛出异常并被控制器捕获,但不保存记录(并且我们要求即使在外部系统出现异常的情况下也要存储部分数据 - 这是不可接受的)。在 的情况下after_commit,异常不会被控制器抛出和拾取,而是部分记录被持久化。这意味着我们无法将异常通知用户(除非我们实现了一些通知推送机制——这太过分了)。

事实证明,我们可以在模型上设置错误after_save,这很好。但是,这是处理这种情况的一个很好的通用模式吗?

4

1 回答 1

1

你在那里没有很多选择。由于您所要求的逻辑本身很复杂,我会将代码移动以将记录保存到其他地方(可能是一个类方法或另一个类,代表一个服务),当有人想要保存这个特定对象时它会有通过这种服务模式。

例如:

class MyModelPersistenceService

    def save_model( model )
      result = model.save
      if result
        call_external_service
      end
    end 

end

服务在 Ruby/Rails 项目中并不常见,但它们非常适合这种用法(滥用 AR 回调通常会导致难以测试对象)。

于 2012-06-07T21:38:55.910 回答