ruby-on-rails - 给观察者动态添加回调方法

标签 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 方法以在创建后强制触发观察者?

最佳答案

简而言之,这种行为是由于 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/

相关文章:

ruby-on-rails - 路由关注点为资源定义了不同的参数

mysql - serchkick 找到正确结果后如何重新排序结果?

ruby - 在 ruby 法拉第中保持活力

ruby - 有人可以解释这个 ruby​​ 片段中实例变量 @ 前缀的用法吗?

ruby-on-rails - 如何通过rake任务传递-f specdoc选项

ruby-on-rails - 如何在 Rspec 中测试 Pundit Scopes?

ruby-on-rails - 使用 except 删除哈希键

ruby-on-rails - 索引:正确vs外键:正确(Rails)

Ruby 数组 reverse_each_with_index

ruby-on-rails - RSpec:期望改变多个