动态地向观察者添加回调方法

时间:2014-09-24 21:00:18

标签: ruby-on-rails ruby rspec metaprogramming observer-pattern

我想创建一个匹配器,用于测试观察者是否观察模型。

我决定动态添加方法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方法以在创建后强制触发观察者?

1 个答案:

答案 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                                         # - || -

通过Observer实现的简要介绍。

注意:我正在使用“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_callbackshttps://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回调创建和注册任何方法。因此,在你的测试中捕获它之后没有运气。那个小谜就解决了。