ruby-on-rails - rspec 测试 has_many :through and after_save

标签 ruby-on-rails ruby rspec has-many-through after-save

我有一个(我认为)相对简单的 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 添加到引用其 followersThing 模型之前,这工作正常。也就是说,如果我这样做

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 的修复,这需要调用 saveThing 中进行回调之后:

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/

相关文章:

ruby - RVM、Merb、Rake 和 RSpec

ruby-on-rails - Ruby on Rails 'find' 及其返回值

ruby-on-rails - Rails 3,protect_from_forgery和IE8问题

ruby - Watir 无法找到我在 Chrome 的 DOM 检查器中看到的元素

Ruby——国际化域名

rspec - 在 View 规范之间共享代码

ruby-on-rails - 将 Rails 部署为桌面应用程序

jquery - 如何翻译我的部分 jquery-template 文件中的标签内容

ruby - 编写一个简单的 Ruby 调试器

ruby-on-rails - Rspec测试Rails重定向中的test.host