我们假设我们有以下情况:
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
发生。但是,在例外的情况下,我们想向用户表明这一点(可能设置一个flash[:alert]
在视图中显示。)
我们曾尝试使用ActiveRecord::Callbacks
从模型after_save
和after_commit
中抛出异常,并通过设置警报并可能将异常传递给某个异常来在控制器处理该异常通知系统(如Airbrake)。在after_save
的情况下,模型抛出异常并由控制器捕获,但记录未保存(即使在外部系统出现异常的情况下,我们也要求存储部分数据) - 这是不可接受的)。在after_commit
的情况下,控制器抛出并拾取不的例外,但部分记录是持久的。这意味着我们无法通知用户异常(除非我们实施一些通知推送机制 - 这是一种过度杀伤)。
事实证明,我们可以在after_save
上的模型上设置错误,这很好。但是,这是处理这种情况的良好的一般模式吗?
答案 0 :(得分:1)
你没有很多选择。由于你所要求的逻辑本身很复杂,我会移动代码将记录保存到其他地方(可能是一个类方法或另一个类,代表一个服务),当有人想要保存这个特定的对象时,它会有通过这种服务模式。
例如:
class MyModelPersistenceService
def save_model( model )
result = model.save
if result
call_external_service
end
end
end
Services在Ruby / Rails项目中并不常见,但它们非常适合这种用法(滥用AR回调通常会导致难以测试对象)。