2014-03-30 48 views
0

我有兩個模型ArticleArticleVote。當我摧毀一篇文章投票(用戶取消他的投票)時,我想要改變文章的分數。所以我做了回調。這裏是我的ArticleVote模型是什麼樣子:ActiveRecord更改相關模型屬性

class ArticleVote < ActiveRecord::Base 
    belongs_to :article 
    belongs_to :user 

    before_destroy :before_destroy 

    validates :value, inclusion: {in: [1, -1]} 

    def self.upvote(user, article) 
    cast_vote(user, article, 1) 
    end 

    def self.downvote(user, article) 
    cast_vote(user, article, -1) 
    end 

private 

    def self.cast_vote(user, article, value) 
    vote = ArticleVote.where(user_id: user.id, article_id: article.id).first_or_initialize 
    vote.value = value 
    vote.save! 
    article.score += value 
    article.save! 
    end 

    def before_destroy 
    article.score -= value 
    article.save 
    end 
end 

ArticleVote#destroy測試失敗:

context '#destroy' do 
    let(:user) { FactoryGirl.create(:user) } 
    let(:article) { FactoryGirl.create(:article) } 

    it 'changes article score by negative vote value' do 
    ArticleVote.upvote(user, article) 

    expect{ ArticleVote.where(user: user, article: article).first.destroy }.to change{ article.score }.by -1 
    end 
end 

Failures:

1) ArticleVote voting #destroy should change article score by nevative vote value Failure/Error: expect{ ArticleVote.where(user: user, article: article).first.destroy }.to change{ article.score }.by -1 result should have been changed by -1, but was changed by 0 # ./spec/models/article_vote_spec.rb:32:in `block (4 levels) in '

當我改變我的測試此,它通過:

context '#destroy' do 
    let(:user) { FactoryGirl.create(:user) } 
    let(:article) { FactoryGirl.create(:article) } 

    it 'changes article score by nevative vote value' do 
    ArticleVote.upvote(user, article) 
    vote = ArticleVote.where(user: user, article: article).first 

    expect{ vote.destroy }.to change{ vote.article.score }.by -1 
    end 
end 

不該這兩個是相等的嗎?我的articlevote.article不應該參考相同的實例

+0

什麼是第一次測試失敗時的輸出? – mralexlau

+0

更新失敗 –

回答

2

在您的第一個測試中,您正在內存中創建新的Article對象。 Rails在每次調用article.score時都不會檢查數據庫中的屬性值,因爲它會使所有事情都非常緩慢 - 這些值存儲在內存中(這種緩存結果)。因此article.score在任何時候都不會改變。您需要告訴導軌重新加載數據庫中的所有屬性 - 在change塊內使用article.reload.score

附加說明:

讓我們說,我們所做的:

model_1 = Model.where(<condition>).first 
model_2 = Model.where(<some condition>).first 

兩個model_1和model_2從數據庫中的某一行創建的,但它們在內存不同的對象。因此,當你這樣做:

model_1.some_attribute = 'new value' 
model_1.save 

model_2.some_attribute #=> 'old_value' 

的原因是性能 - Rails的是不會檢查數據庫給定的屬性是否已經數據庫中改變與否。 model_2在創建時執行了sql查詢,在您告訴它這樣做之前不會重新檢查。

但是在大多數情況下,在內存中創建兩個重複的對象沒有意義,最好不要這樣做。在那些對象被創造的地方並不總是那麼明顯。在第一次測試的情況下,問題是ArticleVote.where(user: user, article: article).first.article是您的原始article對象的副本,因此您的before_save回調與model_1, model_2示例具有相同的模式。

避免這樣的問題,最好的辦法是正確使用關聯,包括inverse_of選項,並代替「AssociationClass.create代替AssocatedClass.where(model: model, ...)model.association.create(...)使用model.associations.where(...)(型號:型號,...)

+1

確實如此,但在我看來,我正在做的事情應該會自動發生。此外,這不會引發額外的選擇查詢嗎?如果確實如此,它在生產中看起來非常不理想:我正在從DB獲取文章,更改它的狀態並保存它,然後我再次讀取它以加載剛剛保存的內容。 –

+1

是的,它執行額外的SQL查詢。主要原因是這裏不是理想的模型協會。例如 - 在Article Article模型上使用'upvote'方法而不是'ArticleVote'類似乎更自然。如果你願意,請將其發佈在CodeReview上,我會盡力幫助你重新設計它。 – BroiSatse

+0

會不會,謝謝你的回答 –