我想创建匹配器来测试模型是否被观察者观察到。
我决定动态添加方法 after_create
(如有必要),保存模型实例并检查观察者实例是否收到了 after_create
调用。简化版(full version):
RSpec::Matchers.define :be_observed_by do |observer_name|
match do |obj|
...
observer.class_eval do
define_method(:after_create) {}
end
observer.instance.should_receive(:after_create)
obj.save(validate: false)
...
begin
RSpec::Mocks::verify # run mock verifications
true
rescue RSpec::Mocks::MockExpectationError => e
# here one can use #{e} to construct an error message
false
end
end
end
那是行不通的。没有收到观察者的实例 after_create
调用。
但是如果我像这样修改 app/models/user_observer.rb
中 Observer 的实际代码
class UserObserver
...
def after_create end
...
end
它按预期工作。
我应该怎么做才能动态添加after_create
方法以在创建后强制触发观察者?
最佳答案
简而言之,这种行为是由于 Rails 在初始化时将 UserObserver 回调连接到用户事件。如果此时没有为 UserObserver 定义 after_create
回调,则不会调用它,即使后来添加也是如此。
如果您对关于观察者初始化和连接到 wobserved 类的工作原理的更多细节感兴趣,我在最后发布了一个简短的观察者实现演练。但在我们开始之前,这里有一种方法可以让你的测试工作。现在,我不确定你是否想使用它,也不确定你为什么决定首先在你的应用程序中测试观察者行为,但为了完整性......
在你为匹配器中的观察者执行 define_method(:after_create)
之后,插入对 define_callbacks
的显式调用(一种 protected 方法;请参阅下面的 Observer 实现演练它确实)在观察者实例上。这是代码:
observer.class_eval do
define_method(:after_create) { |user| }
end
observer.instance.instance_eval do # this is the added code
define_callbacks(obj.class) # - || -
end # - || -
Observer 实现的简要介绍。
注意:我正在使用“rails-observers”gem 源(在 Rails 4 中,observers 被移动到一个可选的 gem,默认情况下未安装)。在您的情况下,如果您使用的是 Rails 3.x,实现的细节可能会有所不同,但我相信想法是一样的。
首先,这是启动观察者实例的地方:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/railtie.rb#L24 .基本上,在 ActiveSupport.on_load(:active_record)
中调用 ActiveRecord::Base.instantiate_observers
,即加载 ActiveRecord 库时。
在同一个文件中,您可以看到它如何获取通常在 config/application.rb
中提供的 config.active_record.observers
参数并将其传递给 observers=
定义在这里:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L38
但回到ActiveRecord::Base.instantiate_observers
。它只是循环遍历所有已定义的观察者并为每个观察者调用 instantiate_observer
。这里是 instantiate_observer
实现的地方:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L180 .基本上,它会调用 Observer.instance
(作为单例,观察者只有一个实例),如果尚未完成,它将初始化该实例。
这是观察者初始化的样子:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L340 . IE。调用 add_observer!
。
您可以在此处看到 add_observer!
,以及它调用的 define_callbacks
:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/activerecord/observer.rb#L95 .
此define_callbacks
方法遍历当时 观察者类 (UserObserver) 中定义的所有回调,并创建 "_notify_#{observer_name}_for_#{ callback}"
被观察类(用户)的方法,并注册它们以在被观察类(再次是用户)中的该事件上调用。
在您的情况下,应该是 _notify_user_observer_for_after_create
方法作为 after_create
回调添加到用户。在内部,_notify_user_observer_for_after_create
将调用 UserObserver 类上的 update
,后者又将调用 UserObserver 上的 after_create
,一切都将从那里开始。
但是,在你的情况下,after_create
在 Rails 初始化期间不存在于 UserObserver
中,因此没有为 User.after_create
创建和注册方法> 回调。因此,在你的测试中捕获它之后就没有运气了。这个小谜题解开了。
关于ruby-on-rails - 给观察者动态添加回调方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26026400/