我正在尝试编写一个规范,该规范要求在作用域中的所有实例上调用方法。无法找到一种优雅的方式来做到这一点。
这是我的代码的简化表示:
class MyClass < ActiveRecord::Base
scope :active, where(:status => 'active')
scope :inactive, where(:status => 'inactive')
def some_action
# some code
end
此类由另一个在some_action
上调用MyClass.all
的类使用:
class OtherClass
def other_method
MyClass.all.each do |item|
item.some_action
end
end
我想将其更改为:
class OtherClass
def other_method
MyClass.active.each do |item|
item.some_action
end
end
为了测试这种行为,我可以简单地MyClass.stub(:active)
,返回一个存根数组,并期望每个存根上有some_action
。但我不喜欢这种方法,因为它暴露了太多的实现细节。
我更喜欢的东西更像是any_instance_in_scope
。然后我可以简单地将我的规范写成:
MyClass.any_instance_in_scope(:active).should_receive(:some_action)
有没有办法实现这个目标?
答案 0 :(得分:4)
首先,MyClass.all.some_action
不起作用,因为MyClass#some_action
是实例方法,同时MyClass#all
返回Array
- 所以当你执行MyClass.all.some_action
时1}}你实际上是在呼叫Array#some_action
。
另外,请注意MyClass.all
和MyClass.active
返回不同的类:
MyClass.active.class # => ActiveRecord::Relation
MyClass.active.all.class # => Array
我不确定你的some_action
应该做什么......我想你可能想做的一些选择:
如果some_action
正在过滤数组,则应将其转换为另一个范围,执行以下操作:
class MyClass < ActiveRecord::Base
scope :active, where(:status => 'active')
scope :inactive, where(:status => 'inactive')
scope :some_action, ->(color_name) { where(color: color_name) }
end
然后使用MyClass.active.some_action('red').all
调用它。如果您只想要第一个结果MyClass.active.some_action('red').first
。
scope
这是一个很好的答案(及其原因):Testing named scopes with RSpec。
假设您确实希望将MyClass#some_action
定义为实例方法。然后,您可以尝试这样做:
class MyClass < ActiveRecord::Base
scope :active, where(status: 'active')
scope :inactive, where(status: 'inactive')
def some_action
self.foo = 'bar'
self
end
end
在这种情况下,您可以使用MyClass.active.last.some_action
执行它,因为#last
将返回一个实例,而不是整个数组。
some_action
我相信你应该只是按照期望进行测试:
MyClass.should_receive(:some_action).at_least(:once)
MyClass.active.last.some_action
关于此问题的进一步讨论:How to say any_instance should_receive any number of times in RSpec
假设你真的想要运行MyClass.active.some_action
。我建议你先试试这个(与选项#2相同的例子):
class MyClass < ActiveRecord::Base
scope :active, where(status: 'active')
scope :inactive, where(status: 'inactive')
def some_action
self.foo = 'bar'
self
end
end
然后使用MyClass.active.all.map{|my_class| my_class.some_action }
运行。
现在,如果确实想要实现MyClass.active.some_action
- 您希望在ActiveRecord :: Relation的所有实例上执行some_action
(我不喜欢不推荐),执行此操作:
class MyClass < ActiveRecord::Base
scope :active, where(status: 'active')
scope :inactive, where(status: 'inactive')
def some_action
# really do it
end
end
和...
class ActiveRecord::Relation
# run some_action over all instances
def some_action
to_a.each {|object| object.some_action }.tap { reset }
end
end
同样,我不建议这样做。
some_action
与选项#2相同:
MyClass.should_receive(:some_action).at_least(:once)
MyClass.active.last.some_action
注意:所有代码都使用Ruby 2.0.0-p0。安装并使用它,很有趣! : - )