Rails Rspec allow_any_instance_of为ActiveRecord对象属性

时间:2016-11-10 19:35:37

标签: ruby-on-rails rspec rails-activerecord stubbing

我有下一个问题。当我尝试使用 allow_any_instance_of 来存储ActiveRecord模型的实例方法时,我收到错误消息“模型未实现#method”,但是如果我向数据库发送实际请求以进行创建或在此存根之前选择此模型的实例对象,我没有此消息,一切正常! 我在控制台中有同样的问题

Reloading...

>> Search.method_defined?(:tickets_count)
=> false

>> Search.method_defined?(:affiliate_id)
=> false

>> Search.last
  Search Load (3.9ms)  SELECT  "searches".* FROM "searches"  ORDER BY "searches"."id" DESC LIMIT 1
=> #<Search:0x007fe2aecf2900
 id: 515711,
 tickets_count: 1,
 affiliate_id: nil

>> Search.method_defined?(:affiliate_id)
=> true

>> Search.method_defined?(:tickets_count)
=> true

有人能解释一下,到底发生了什么事,拜托?))

1 个答案:

答案 0 :(得分:9)

您在这里遇到的是activerecord对数据库属性访问器方法的延迟实例化。

TL; DR您可以使用类似https://github.com/rspec/rspec-rails/issues/1357

的内容

基本上,ActiveRecord :: Base类在他们需要之前不会定义任何基于数据库列的setter / getter,通常是通过方法缺失的钩子,尽管可能有其他东西可能导致它们被定义,例如,调用Search.find_by_affiliate_id也可能导致定义加载。

我可以提供高级解释,说明它如何/为什么以它的方式工作,但是可能有更好/更新的资源可以更好地解释它,或者你可以通读ActiveRecord源代码虽然有点迟钝,但却是理解其行为的合理选择。

那么为什么不定义这些方法?

当通过继承自ActiveRecord::Base创建新的ActiveRecord类时,所有类都知道它是预期的表名,但是如果没有连接到数据库,则不知道该表有哪些列。在不知道存在哪些列的情况下,它无法知道要定义哪些读取器/写入器。 ActiveRecord在加载时不会主动查询数据库,我认为这是一件非常好的事情,因为它允许您加载代码而无需迁移的连接数据库。

ActiveRecord的作用是挂钩method_missingresponds_to?等,以确定何时在未定义的ActiveRecord类上调用方法,尝试加载当前的db模式并定义reader / writer / accessor方法本身为它找到的每一列,然后重试方法调用以查看它是否已定义。

在上面的示例中,可能定义属性的.last调用,而是终端运行的.inspect调用以打印实例Search个对象。

要对此进行测试,您可以重新运行上面的示例,但将Search.last替换为(s = Search.last).nil?。如果你这样做了,我敢打赌Search.method_defined?(:affiliate_id)仍然是假的。

希望这可以解释为什么这些方法最初没有定义,但后来又定义了。这也是您无法使用alias_method :aliased_affiliate_id, :affiliate_id的原因,因为在加载时没有定义方法affiliat_id,因此会失败。

对于您要执行的操作,您需要确定是否需要有效的数据库连接以运行这些规范。

如果是这样,您可以触发ActiveRecord类加载其表定义并在规范中定义其读/写属性,以便通过。

这里有人遇到与rspec相同的问题,其中一个猴子补丁解决方案加载了activerecord类的定义:

https://github.com/rspec/rspec-rails/issues/1357