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_action動作しませんが、一方で-- を返します。そのため、実際に を呼び出していることになります。MyClass#some_actionMyClass#allArrayMyClass.all.some_actionArray#some_action

また、異なるクラスに注意MyClass.allして返します。MyClass.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.

scopeRSpecでテストする方法

これはそれに対する良い答えです (そしてその理由): Testing named scopes with 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_actionRSpecでテストする方法

期待してテストするだけでいいと思います:

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

これに関する追加の議論: How to say any_instance should_receive any number 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_actionActiveRecord::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_actionRSpecでテストする方法

オプション #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 に答える