这就是我不喜欢 ActiveRecord 回调的原因——因为如果您不想与回调有任何关系(例如,您在回调中调用外部服务),您仍然必须担心会被淘汰。是的,您可以在回调中存根方法,但这是同样的问题,实际上它有点糟糕,因为现在您担心在一个您不想与之有任何关系的方法。
像往常一样,这里有多个选项。
我过去经常使用的一个选项是,向您的回调添加一个条件,默认情况下将其关闭。所以你的 Post 类可能看起来像:
class Post
before_save :sync_with_store, :if => :syncing_with_store?
def syncing_with_store?; @syncing_with_store; end
attr_writer :syncing_with_store
def sync_with_store
# make an HTTP request or something
end
end
现在,无论您真正想在哪里调用回调(可能在您的控制器中或任何地方),您都可以在调用post.save 之前设置post.syncing_with_store = true。
这种方法的缺点是,您(以及与您一起工作的其他开发人员)必须牢记这一点,而且您必须这样做并不是很明显。另一方面,如果您忘记执行此操作,则不会发生任何不好的事情。
另一种选择是使用假类。假设您有一个 Post,它在保存时将其数据推送到外部数据存储。您可以提取执行推送到单独类(例如 Pusher)的代码,该类可在 Post.pusher_service 访问。但是,默认情况下,这将被设置为一个虚假的 Pusher 类,它响应相同的接口但什么都不做。比如:
class Post
class << self
attr_accessor :pusher_service
end
self.pusher_service = FakePostPusher
before_save :sync_with_store
def sync_with_store
self.class.pusher_service.run(self)
end
end
class FakePostPusher
def self.run(post)
new(post).run
end
def initialize(post)
@post = post
end
def run
# do nothing
end
end
class PostPusher < FakePostPusher
def run
# actually make the HTTP request or whatever
end
end
在您的生产环境文件中,您将设置Post.pusher_service = Pusher。在单个测试或测试用例中,您可以创建 Post 的子类 -- let(:klass) { Class.new(Post) } -- 并设置 klass.pusher_service = Pusher(这样您就不会永久设置它并影响未来的测试)。
我一直在尝试的第三种方法是:根本不使用 ActiveRecord 回调。这是我从Gary Bernhardt's screencasts 那里得到的(顺便说一句,这非常了不起)。相反,定义一个服务类来包装创建帖子的行为。比如:
class PostCreator
def self.run(attrs={})
new(attrs).run
end
def initialize(attrs={})
@post = Post.new(attrs)
end
def run
if @post.save
make_http_request
return true
else
return false
end
end
def make_http_request
# ...
end
end
PostCreator.run(attrs) 这种方式实际上是创建帖子而不是通过帖子的方式。现在要在 Post 中测试保存,无需存根回调。如果您想测试 PostCreator 过程,没有什么神奇的,您可以轻松地删除您想要的任何方法或独立测试它们。 (你可能会争辩说,在这里存根方法与存根 AR 回调是一样的,但我认为它更明确地发生了什么。)显然这只处理帖子创建,但你也可以对帖子更新做同样的事情。
不管怎样,不同的想法,选择你的毒药。