rspec测试has_many:through和after_save [英] rspec testing has_many :through and after_save
问题描述
我(我认为)与联接表具有相对简单的has_many :through
关系:
I have an (I think) relatively straightforward has_many :through
relationship with a join table:
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测试(我知道这些不一定是好的测试,这些只是为了说明正在发生的事情):
And these rspec tests (I know these are not necessarily good tests, these are just to illustrate what's happening):
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
添加到引用其followers
的Thing
模型中.也就是说,如果我这样做
This works fine UNTIL I add an after_save
to the Thing
model that references its followers
. That is, if I do
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"
,则"stdout"上会显示"HI",因此我知道正在调用该回调.如果我注释掉followers.each
行,则测试将再次通过.
Then the second test fails - i.e., the relationship is still added to the join table, but @thing.followers
returns an empty array. Furthermore, that part of the callback never gets called (as if followers
is empty within the model). If I add a puts "HI"
in the callback before the followers.each
line, the "HI" shows up on stdout, so I know the callback is being called. If I comment out the followers.each
line, then the tests pass again.
如果我全部通过控制台执行此操作,则效果很好.也就是说,我可以做到
If I do this all through the console, it works fine. I.e., I can do
>> 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中失败?我做错什么了吗?
Why is this failing in rspec? Am I doing something horribly wrong?
更新
如果我手动将Thing#followers
定义为
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
这使我相信也许是我用:source
错误地定义了has_many :through
吗?
This leads me to believe that perhaps I am defining my has_many :through
with :source
incorrectly?
更新
我创建了一个最小的示例项目,并将其放在github上: https://github.com/dantswain/RspecHasMany
I've created a minimal example project and put it on github: https://github.com/dantswain/RspecHasMany
另一个更新
非常感谢@PeterNixey和@kikuchiyo在下面的建议.最终答案是两个答案的结合,我希望我能在两个答案之间分配荣誉.我已经用我认为是最干净的解决方案更新了github项目,并推送了更改: https://github.com/dantswain/RspecHasMany
Thanks a ton to @PeterNixey and @kikuchiyo for their suggestions below. The final answer turned out to be a combination of both answers and I wish I could split credit between them. I've updated the github project with what I think is the cleanest solution and pushed the changes: https://github.com/dantswain/RspecHasMany
如果有人可以给我一个真正扎实的解释,我仍然会喜欢的.对我来说,最麻烦的是,为什么在最初的问题陈述中,如果我注释掉对followers
的引用,一切(回调函数本身的操作除外)都将起作用.
I would still love it if someone could give me a really solid explanation of what is going on here. The most troubling bit for me is why, in the initial problem statement, everything (except the operation of the callback itself) would work if I commented out the reference to followers
.
推荐答案
过去,我也遇到过类似的问题,可以通过重新加载关联(而不是父对象)来解决.
I've had similar problems in the past that have been resolved by reloading the association (rather than the parent object).
如果在RSpec中重新加载thing.followers
,是否可以正常工作?
Does it work if you reload thing.followers
in the RSpec?
it "should have followers" do
@thing.followers.reload
@thing.followers.should == [@user]
end
编辑
如果(如您所提到的)您遇到了无法触发回调的问题,则可以在对象本身中重新加载:
If (as you mention) you're having problems with the callbacks not getting fired then you could do this reloading in the object itself:
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遇到不重载关联的问题,但是我自己遇到了相同类型的问题
I don't know why RSpec has issues with not reloading associations but I've hit the same types of problems myself
编辑2
尽管@dantswain确认followers.reload
有助于缓解某些问题,但仍然无法解决所有问题.
Although @dantswain confirmed that the followers.reload
helped alleviate some of the problems it still didn't fix all of them.
为此,该解决方案需要来自@kikuchiyo的修复程序,该修复程序要求在Thing
中执行回调后调用save
:
To do that, the solution needed a fix from @kikuchiyo which required calling save
after doing the callbacks in Thing
:
describe Thing do
before :each do
...
@user.things << @thing
@thing.run_callbacks(:save)
end
...
end
最终建议
我相信这是由于在has_many_through
操作上使用了<<
而引起的.我看不到<<
实际上应该完全触发您的after_save
事件:
I believe this is happening because of the use of <<
on a has_many_through
operation. I don't see that the <<
should in fact trigger your after_save
event at all:
您当前的代码是这样:
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
没有被调用.我认为这是正确的行为.
and the problem is that the do_stuff
is not getting called. I think this is the correct behaviour though.
让我们通过RSpec:
Let's go through the 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
对象-它只是在联接表中创建了一个条目.
The problem is that the third step does not actually require the thing
object to be resaved - its simply creating an entry in the join table.
如果您要确保@user确实保存了通话,则可能会获得所需的效果:
If you'd like to make sure that the @user does call save you could probably get the effect you want like this:
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
来更新用户.
You may also find that the after_save
callback is in fact on the wrong object and that you'd prefer to have it on the relationship object instead. Finally, if the callback really does belong on the user and you do need it to fire after creating the relationship you could use touch
to update the user when a new relationship is created.
这篇关于rspec测试has_many:through和after_save的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!