我有两个模型Article
和ArticleVote
。当我销毁一篇文章投票(用户取消他的投票)时,我想要改变文章的分数。所以我做了一个回调。这是我的ArticleVote模型的样子:
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
故障:
1)ArticleVote投票#destroy应该通过创造性的投票价值来改变文章得分 失败/错误:期待{ArticleVote.where(user:user,article:article).first.destroy} .to change {article.score} .by -1 结果应该已经被-1更改,但被更改为0 './spec/models/article_vote_spec.rb:32:in'块(4级)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
这两个不应该相同吗?我的article
和vote.article
不应该引用相同的实例吗?
答案 0 :(得分:2)
在第一次测试中,您将在内存中创建新的Article对象。每次调用article.score
时,Rails都不会检查db中的属性值,因为它会使一切都非常慢 - 这些值存储在内存中(它有点缓存结果)。因此article.score
在任何时候都不会改变。您需要告诉rails重新加载数据库中的所有属性 - 在article.reload.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'
原因是性能--Rails不会检查数据库中是否给定属性在数据库中已更改。 model_2
在创建时执行了sql查询,并且在您告诉它之前不会重新检查。
但是在大多数情况下,在内存中创建两个重复对象没有意义,最好不要这样做。在创造这些对象的地方并不总是那么明显。如果是第一次测试,问题是ArticleVote.where(user: user, article: article).first.article
与原始article
对象重复,因此您的before_save
回调遵循与model_1, model_2
示例相同的模式。
避免此类问题的最佳方法是正确使用关联,包括inverse_of
选项并使用model.associations.where(...)
代替AssocatedClass.where(model: model, ...)
或model.association.create(...)
代替'AssociationClass .create(model:model,...)