dan*_*ain 5 ruby rspec ruby-on-rails after-save has-many-through
我有一个(我认为)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
Run Code Online (Sandbox Code Playgroud)
这些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
Run Code Online (Sandbox Code Playgroud)
这工作正常UNTIL我添加一个引用它after_save的Thing模型followers.也就是说,如果我这样做
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
Run Code Online (Sandbox Code Playgroud)
然后第二个测试失败 - 即,关系仍然添加到连接表,但@thing.followers返回一个空数组.此外,回调的那部分永远不会被调用(好像followers在模型中是空的).如果我在行puts "HI"之前的回调中添加一个followers.each,则"HI"出现在stdout上,所以我知道正在调用回调.如果我注释掉该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]
Run Code Online (Sandbox Code Playgroud)
为什么这在rspec中失败了?我做错了什么吗?
更新
如果我手动定义Thing#followers为,一切都有效
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
Run Code Online (Sandbox Code Playgroud)
这使我相信,也许我定义我has_many :through有:source错误?
更新
我创建了一个最小的示例项目并将其放在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
Run Code Online (Sandbox Code Playgroud)
编辑
如果(正如你所提到的)你遇到了没有被解雇的回调问题,那么你可以在对象本身重新加载:
class Thing < ActiveRecord::Base
after_save { followers.reload}
after_save :do_stuff
...
end
Run Code Online (Sandbox Code Playgroud)
要么
class Thing < ActiveRecord::Base
...
def do_stuff
followers.reload
...
end
end
Run Code Online (Sandbox Code Playgroud)
我不知道为什么RSpec有没有重新加载关联的问题,但我自己也遇到了同样类型的问题
编辑2
尽管@dantswain确认这followers.reload有助于缓解一些问题,但仍然无法解决所有这些问题.
要做到这一点,解决方案需要@kikuchiyo的修复,这需要save在执行回调之后调用Thing:
describe Thing do
before :each do
...
@user.things << @thing
@thing.run_callbacks(:save)
end
...
end
Run Code Online (Sandbox Code Playgroud)
最后的建议
我相信这是因为<<在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
Run Code Online (Sandbox Code Playgroud)
而问题是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
Run Code Online (Sandbox Code Playgroud)
问题是第三步实际上并不需要thing重新保存对象 - 它只是在连接表中创建一个条目.
如果你想确保@user确实调用了save,你可能会得到你想要的效果,如下所示:
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
Run Code Online (Sandbox Code Playgroud)
您可能还会发现after_save回调实际上是在错误的对象上,而您更愿意将其放在关系对象上.最后,如果回调确实属于用户,并且您确实需要在创建关系后触发它,则可以在创建touch新关系时更新用户.