我有一个(我认为)相对简单的 has_many :through
与连接表的关系:
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_save
添加到引用其 followers
的 Thing
模型之前,这工作正常。也就是说,如果我这样做
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”会显示在标准输出上,因此我知道正在调用回调。如果我注释掉 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 中失败了?我是不是做错了什么?
更新
如果我手动将 Thing#followers
定义为
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
这让我相信也许我用 :source
错误地定义了我的 has_many :through
?
更新
我创建了一个最小的示例项目并将其放在 github 上:https://github.com/dantswain/RspecHasMany
另一个更新
非常感谢@PeterNixey 和@kikuchiyo 在下面提出的建议。最后的答案结果是两个答案的组合,我希望我可以在它们之间分配功劳。我已经用我认为最干净的解决方案更新了 github 项目并推送了更改:https://github.com/dantswain/RspecHasMany
如果有人能给我一个关于这里发生的事情的真正可靠的解释,我仍然会很高兴。对我来说最麻烦的一点是,为什么在最初的问题陈述中,如果我注释掉对 followers
的引用,所有内容(回调本身的操作除外)都会起作用。
最佳答案
我过去遇到过类似的问题,这些问题已通过重新加载关联(而不是父对象)得到解决。
如果你重新加载它是否有效thing.followers
在 RSpec 中?
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 的修复,这需要调用 save
在 Thing
中进行回调之后:
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
callback 实际上在错误的对象上,您更愿意将它放在关系对象上。最后,如果回调确实属于用户并且您确实需要在创建关系后触发它,您可以使用 touch
在创建新关系时更新用户。
关于ruby-on-rails - rspec 测试 has_many :through and after_save,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8914121/