2012-01-18 64 views
4

我有一個連接表的(我認爲)相對簡單has_many :through關係:RSpec的測試的has_many:通過和after_save的

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_saveThing模型引用其followers這工作得很好。也就是說,如果我做

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 

然後第二測試失敗 - 即,關係仍然加到連接表,但是@thing.followers返回一個空數組。此外,回調的那部分永遠不會被調用(就好像followers在模型中是空的)。如果我在followers.each行之前的回調中添加puts "HI",則「HI」出現在stdout上,所以我知道該回調被調用。如果我註釋掉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中失敗?我在做可怕的錯誤嗎?

更新

一切正常,如果我手動定義​​爲

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

這使我相信,也許我定義我has_many :through:source不正確?

更新

我創建了一個小例子項目,並把它放在github上:https://github.com/dantswain/RspecHasMany

另一個更新

由於一噸@PeterNixey和@kikuchiyo對他們的建議下面。最終答案證明是兩個答案的組合,我希望我可以在他們之間分配信貸。我已經用我認爲是最乾淨的解決方案更新了github項目並推送了更改:https://github.com/dantswain/RspecHasMany

如果有人能夠給我一個關於這裏發生的事情的真實可靠解釋,我仍然會喜歡它。對我來說最麻煩的一點是,在最初的問題陳述中,如果我註釋了對followers的引用,所有內容(除了回調本身的操作)都可以工作。

回答

7

我已經有過類似的問題,已通過重新加載解析關聯(而不是父對象)。

如果您在RSpec中重新加載thing.followers,它會工作嗎?

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這需要調用saveThing做回調後:

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 

的問題是,第三步實際上並不需要被重新保存在thing對象 - 它只是建立在連接表中的條目。

如果你想確保在@user不叫救你也許可以得到你想要這樣的效果:

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在創建新關係時更新用戶。

+0

不錯!這幾乎奏效,但它不會運行回調。 – dantswain

+0

真是痛苦。我已經在更新的答案中提出了另一個建議 –

+0

double重載(在規範和回調中)都是必需的,但我還需要調用@ thing.save(或@ thing.run_callbacks(:save) )。我有點困惑 - 如果我已經解決了我的問題,那麼感謝你的建議和kikuchiyo的建議。 – dantswain

1

我的猜測是你需要通過做@thing.reload(我敢肯定有一種方法可以避免這種情況,但是這可能會讓你的測試通過,然後你可以找出你已經去了哪裏錯誤)。

幾個問題:

我沒有看到你在規範調用@thing.save。你是否這樣做,就像在你的控制檯例子中一樣?

爲什麼你要在你的控制檯測試中打電話t.save而不是u.save,考慮到你正在將t推到u?保存u應該觸發保存到t,得到您想要的最終結果,我認爲這將「更有意義」,因爲您確實在使用u而不是t

+0

向@thing添加重新加載不會使測試通過。我感覺'''操作符會自動保存(我認爲第一次測試通過這一事實就證明了這一點)。無論如何,我可以將'@ thing.save'和'@ user.save'添加到測試設置,並且不會使測試通過。我只是在控制檯中調用't.save'來說服我自己''after_save'正在被調用。 'after_save'需要放在'Thing'上,因爲這正是我正在努力的方向(例如,在一件事情被保存之後,通知它的所有追隨者)。 – dantswain

+0

我沒有意識到''''自動保存了這兩條記錄。你說對了,如果第一次測試沒有被保存,那麼通過第一次測試就沒有什麼意義了......我會多考慮一點。 – theIV

+0

看看我的更新。如果我手動定義'Thing#followers' - 也許我的'has_many:through'是錯誤的,它會起作用嗎? – dantswain

2

修訂ANSWER ** 這通過RSpec的,沒有磕碰,爲節省運行回調(after_save的包括回調),並檢查@ thing.followers是不是要訪問它的元素之前空。 (;

describe Thing do 
    before :each do 
    @user = User.create(:name => "Fred"); 
    @thing = Thing.new(:name => 'Foo') 
    @user.things << @thing 
    @thing.run_callbacks(:save) 
    end 

    it "should have created a relationship" do 
    @thing.followers.should == [@user] 
    puts @thing.followers.inspect 
    end 
end 
class Thing < ActiveRecord::Base 
    after_save :some_function 
    has_many :user_following_thing_relationships 
    has_many :followers, :through => :user_following_thing_relationships, :source => :user 

    def some_function 
    the_followers = followers 
    unless the_followers.empty? 
     puts "accessing followers here: the_followers = #{the_followers.inspect}..." 
    end 
    end 
end 

原來的答案**

我能得到的東西與after_save的回調函數的工作,只要我沒有的do_stuff身體/塊中引用followers。你需要參考followers你正在調用的真實方法是從after_save

更新後的代碼來存根回調現在模型可以保持你需要它,我們顯示@ thing.fol降低的確按照我們的預期設置,我們可以通過after_save在不同的規範中研究do_stuff/some_function的功能。

我把這裏的代碼的副本:https://github.com/kikuchiyo/RspecHasMany

和規格的傳球事*代碼如下:

# thing_spec.rb 
require 'spec_helper' 

describe Thing do 
    before :each do 
     Thing.any_instance.stub(:some_function) { puts 'stubbed out...' } 
     Thing.any_instance.should_receive(:some_function).once 
     @thing = Thing.create(:name => "Foo"); 
     @user = User.create(:name => "Fred"); 
     @user.things << @thing 
    end 

    it "should have created a relationship" do 
     @thing.followers.should == [@user] 
     puts @thing.followers.inspect 
    end 
end 
# thing.rb 
class Thing < ActiveRecord::Base 
    after_save :some_function 
    has_many :user_following_thing_relationships 
    has_many :followers, :through => :user_following_thing_relationships, :source => :user 

    def some_function 
     # well, lets me do this, but I cannot use @x without breaking the spec... 
     @x = followers 
     puts 'testing puts hear shows up in standard output' 
     x ||= 1 
     puts "testing variable setting and getting here: #{x} == 1\n\t also shows up in standard output" 
     begin 
      # If no stubbing, this causes rspec to fail... 
      puts "accessing followers here: @x = #{@x.inspect}..." 
     rescue 
      puts "and this is but this is never seen." 
     end 
    end 
end 
+0

是的,我需要在回調中訪問「追隨者」。我在問題中說過,如果我評論這條線,那麼測試就會通過。有趣的是@ @ @ @,我們可以分配'@x = followers'和測試通過。 – dantswain

+0

很酷。我們可以讓您的測試通過,並通過剔除:do_stuff來讓您按照需要的方式對代碼進行建模。將這添加到before_each的頂部將通過我們的測試'Thing.any_instance.stub(:some_function){puts'hi'}'。 – kikuchiyo

+1

答案已經更新。 – kikuchiyo