7

私は(私が思うに)has_many :through結合テーブルとの比較的単純な関係を持っています:

class User < ActiveRecord::Base
  has_many :user_following_thing_relationships
  has_many :things, :through => :user_following_thing_relationships
end

class Thing < ActiveRecord::Base
  has_many :user_following_thing_relationships
  has_many :followers, :through => :user_following_thing_relationships, :source => :user
end

class UserFollowingThingRelationship < ActiveRecord::Base
  belongs_to :thing
  belongs_to :user
end

そして、これらの rspec テスト (これらは必ずしも良いテストではないことはわかっています。何が起こっているかを説明するためのものです):

describe Thing do     
  before(:each) do
    @user = User.create!(:name => "Fred")
    @thing = Thing.create!(:name => "Foo")    
    @user.things << @thing
  end

  it "should have created a relationship" do
    UserFollowingThingRelationship.first.user.should == @user
    UserFollowingThingRelationship.first.thing.should == @thing
  end

  it "should have followers" do
    @thing.followers.should == [@user]
  end     
end

これは、を参照するモデルに を追加するまで問題なく動作after_saveします。つまり、もしそうならThingfollowers

class Thing < ActiveRecord::Base
  after_save :do_stuff
  has_many :user_following_thing_relationships
  has_many :followers, :through => :user_following_thing_relationships, :source => :user

  def do_stuff
    followers.each { |f| puts "I'm followed by #{f.name}" }
  end
end

その後、2 番目のテストは失敗します。つまり、関係は引き続き結合テーブルに追加され@thing.followersますが、空の配列が返されます。さらに、コールバックのその部分が呼び出されることはありません (あたかもfollowersモデル内で空であるかのように)。puts "HI"行の前にコールバックにa を追加するとfollowers.each、「HI」が標準出力に表示されるので、コールバックが呼び出されていることがわかります。行をコメントアウトするとfollowers.each、テストは再びパスします。

これをすべてコンソールから行うと、正常に動作します。つまり、できる

>> t = Thing.create!(:name => "Foo")
>> t.followers # []
>> u = User.create!(:name => "Bar")
>> u.things << t
>> t.followers  # [u]
>> t.save    # just to be super duper sure that the callback is triggered
>> t.followers  # still [u]

これがrspecで失敗するのはなぜですか? 私はひどく間違ったことをしていますか?

アップデート

手動で次のように定義すると、すべてが機能Thing#followersします

def followers
  user_following_thing_relationships.all.map{ |r| r.user }
end

これは、おそらく私が my has_many :throughwith を:source間違って定義していると信じるように導きますか?

アップデート

最小限のサンプル プロジェクトを作成し、github に配置しました: https://github.com/dantswain/RspecHasMany

別のアップデート

@PeterNixey と @kikuchiyo の以下の提案に感謝します。最終的な答えは両方の答えの組み合わせであることが判明したので、それらの間でクレジットを分割できたらいいのにと思います。最もクリーンなソリューションと思われるもので github プロジェクトを更新し、変更をプッシュしました: https://github.com/dantswain/RspecHasMany

誰かがここで何が起こっているのかについて本当にしっかりした説明をしてくれるなら、私はまだそれが大好きです. 私にとって最も厄介な点は、最初の問題ステートメントで、への参照をコメントアウトした場合にすべて (コールバック自体の操作を除く) が機能する理由ですfollowers

4

3 に答える 3

9

私は過去に同様の問題を抱えていましたが、(親オブジェクトではなく)関連付けを再ロードすることで解決されました。

thing.followersRSpecでリロードすると機能しますか?

it "should have followers" do
  @thing.followers.reload
  @thing.followers.should == [@user]
end 

編集

(あなたが言うように)コールバックが起動されないという問題がある場合は、オブジェクト自体でこのリロードを行うことができます:

class Thing < ActiveRecord::Base
  after_save { followers.reload}
  after_save :do_stuff
  ...
end

また

class Thing < ActiveRecord::Base
  ...
  def do_stuff
    followers.reload
    ...
  end
end

RSpecでアソシエーションをリロードしないという問題が発生する理由はわかりませんが、同じタイプの問題が発生しました。

編集2

@dantswainは、followers.reloadいくつかの問題を軽減するのに役立ったことを確認しましたが、それでもすべてを修正することはできませんでした。

そのためには、ソリューションには@kikuchiyoからの修正が必要でありsave、コールバックを実行した後に呼び出す必要がありましたThing

describe Thing do
  before :each do
    ...
    @user.things << @thing
    @thing.run_callbacks(:save)
  end 
  ...
end

最終的な提案

これは<<has_many_through操作での使用が原因で発生していると思います。実際にイベントを<<トリガーする必要があるとは思いません。after_save

現在のコードは次のとおりです。

describe Thing do
  before(:each) do
    @user = User.create!(:name => "Fred")
    @thing = Thing.create!(:name => "Foo")    
    @user.things << @thing
  end
end

class Thing < ActiveRecord::Base
  after_save :do_stuff
  ...

  def do_stuff
   followers.each { |f| puts "I'm followed by #{f.name}" }
  end
end

問題は、do_stuffが呼び出されないことです。しかし、これは正しい動作だと思います。

RSpecを見てみましょう:

describe Thing do
  before(:each) do
    @user = User.create!(:name => "Fred")
    # user is created and saved

    @thing = Thing.create!(:name => "Foo")    
    # thing is created and saved

    @user.things << @thing
    # user_thing_relationship is created and saved
    # no call is made to @user.save since nothing is updated on the user
  end
end

問題は、3番目のステップでは、実際にthingオブジェクトを再保存する必要がないことです。つまり、結合テーブルにエントリを作成するだけです。

@userがsaveを呼び出すことを確認したい場合は、おそらく次のような効果を得ることができます。

describe Thing do
  before(:each) do
    @thing = Thing.create!(:name => "Foo")    
    # thing is created and saved

    @user = User.create!(:name => "Fred")
    # user is created BUT NOT SAVED

    @user.things << @thing
    # user_thing_relationship is created and saved
    # @user.save is also called as part of the addition
  end
end

また、after_saveコールバックが実際には間違ったオブジェクトにあり、代わりにリレーションシップオブジェクトにコールバックを設定したい場合もあります。最後に、コールバックが実際にユーザーに属していて、関係の作成後にコールバックを起動する必要があるtouch場合は、新しい関係が作成されたときにユーザーを更新するために使用できます。

于 2012-01-22T18:06:35.117 に答える
1

Thing私の推測では、実行してインスタンスをリロードする必要があると思います@thing.reload(これを回避する方法があると確信していますが、最初はテストに合格する可能性があり、その後、どこが間違っているかを突き止めることができます)。

いくつかの質問:

@thing.saveあなたがあなたのスペックを呼び出しているのを見ません。コンソールの例と同じように、それを行っていますか?

にプッシュしていることを考えると、コンソールテストではt.saveなく、なぜ呼び出しているのですか? 保存すると への保存がトリガーされ、必要な最終結果が得られます。実際に作業しているのはではなくであることを考えると、「より理にかなっている」と思います。u.savetuutut

于 2012-01-18T17:35:01.613 に答える