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

查看:17
本文介绍了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",HI"会显示在 stdout 上,所以我知道正在调用回调.如果我注释掉 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 定义为

Everything works if I manually define Thing#followers as

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:

您当前的代码是这样的:

Your current code is this:

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 确实调用了 save 你可能会得到你想要的效果:

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天全站免登陆