2

我正在尝试编写一个规范,该规范期望在范围内的所有实例上调用一个方法。找不到优雅的方法来做到这一点。

这是我的代码的简化表示:

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)

有什么办法可以做到这一点?

4

1 回答 1

4

首先,MyClass.all.some_actionis not going to work since MyClass#some_actionis an instance method,同时MyClass#all返回一个Array-- 所以当你这样做时,MyClass.all.some_action你实际上是在调用Array#some_action.

另外,注意MyClass.allMyClass.active返回不同的类:

MyClass.active.class # => ActiveRecord::Relation
MyClass.active.all.class # => Array

我不确定你some_action应该做什么......我想你可能想要做的一些选择:

选项#1:缩小数据库查询

如果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使用 RSpec进行测试

这是一个很好的答案(以及原因):使用 RSpec 测试命名范围

选项 #2:在实例之上执行操作

假设您确实希望将其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使用 RSpec进行测试

我相信你应该简单地用期望来测试它:

MyClass.should_receive(:some_action).at_least(:once)
MyClass.active.last.some_action

对此的补充讨论:How to say any_instance should_receive any numbered in RSpec

选项#3:集体行动

假设你真的想跑步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——你想some_action在 ActiveRecord::Relation 的所有实例上执行(我不推荐),这样做:

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使用 RSpec进行测试

与选项 #2 相同的情况:

MyClass.should_receive(:some_action).at_least(:once)
MyClass.active.last.some_action

注意:所有代码都使用 Ruby 2.0.0-p0。安装使用,很好玩!:-)

于 2013-03-13T01:48:50.683 回答