rspec测试has_many:through和after_save [英] rspec testing has_many :through and after_save

查看:92
本文介绍了rspec测试has_many:through和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添加到引用其followersThing模型中.也就是说,如果我这样做

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆