2014-03-30 6 views
0

두 모델 ArticleArticleVote이 있습니다. 기사 투표를 취소하면 (사용자가 투표를 취소 함) 기사의 점수를 변경하고 싶습니다. 그래서 콜백했다.ActiveRecord 관련 모델 속성 변경

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 

Shouldn을 :

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 '

나는이 내 테스트를 변경, 그것은 통과 여기처럼 내 ArticleVote 모델이 모습입니다 이 둘은 동등한가? 내 articlevote.article과 일치해야합니까?

+0

(모델 ... 모델) 무엇 실패 할 때 첫 번째 테스트의 출력입니까? – mralexlau

+0

실패로 업데이트 됨 –

답변

2

첫 번째 테스트에서는 메모리에 새 기사 개체를 만듭니다. 레일스는 article.score을 호출 할 때마다 db의 속성 값을 검사하지 않으므로 모든 것이 매우 느려지므로 그 값은 메모리에 저장됩니다 (결과를 일종의 캐싱이라고합니다). 따라서 article.score은 언제든지 변경되지 않습니다. 데이터베이스에서 모든 속성을 다시로드하려면 레일에 알릴 필요가 있습니다. 을 change 블록 내에 사용하십시오.

추가 설명 :

하자가 우리가했던 말 :

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' 

이유는 성능입니다. 레일스는 주어진 속성이 데이터베이스 내에서 변경되었는지 여부를 데이터베이스가 점검하지 않을 것입니다. model_2 SQL 쿼리를 만들 때 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 쿼리를 수행합니다. 주된 이유는 이상적인 모델 연합이 아닙니다. 예를 들어'ArticleVote'보다는'Article' 모델에서'upvote' 메소드를 사용하는 것이 더 자연스럽게 보일 것입니다. 원한다면 CodeReview에 게시 해주십시오. 재 설계를 도와 드리겠습니다. – BroiSatse

+0

답변 해 주셔서 감사합니다. –