我想创建一个匹配器,用于测试观察者是否观察模型。
我决定动态添加方法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
方法以在创建后强制触发观察者?
答案 0 :(得分:3)
简而言之,这种行为是由于Rails在初始化时将UserObserver回调挂钩到User事件。如果此时没有为UserObserver定义after_create
回调,则即使稍后添加,也不会调用它。
如果您对观察者初始化和与wobserved类的连接如何工作的更多细节感兴趣,最后我在Observer实现中发布了一个简短的演练。但在我们开始之前,这是一种让你的测试工作的方法。现在,我不确定你是否想要使用它,并且不确定为什么你决定在你的应用程序中首先测试观察者行为,但是为了完整性...
在匹配器中为观察者执行define_method(:after_create)
之后,在观察者实例上插入对define_callbacks
的显式调用(受保护的方法;请参阅下面的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 # - || -
注意:我正在使用“rails-observers”宝石源(在Rails 4观察者中被移动到一个可选的gem,默认情况下没有安装)。在你的情况下,如果你在Rails 3.x上,实现的细节可能会有所不同,但我相信这个想法是一样的。
首先,这是观察者的实例化启动的地方:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/railtie.rb#L24。基本上,在ActiveRecord::Base.instantiate_observers
中调用ActiveSupport.on_load(:active_record)
,即加载ActiveRecord库时。
在同一个文件中,您可以看到config.active_record.observers
中通常提供的config/application.rb
参数的处理方式,并将其传递给此处定义的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
(作为Singleton,一个观察者只有一个实例),如果尚未完成该实例,它将初始化该实例。
这是Observer初始化的样子:https://github.com/rails/rails-observers/blob/master/lib/rails/observers/active_model/observing.rb#L340。即致电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
回调创建和注册任何方法。因此,在你的测试中捕获它之后没有运气。那个小谜就解决了。