使用 factory_girl 填充关联的子项

42

我有一个模型 Foo,它 has_many 'Bar'。我为每个对象都有一个 factory_girl 工厂。Bar 的工厂与 Foo 建立了关联;当创建 Bar 时,它将实例化一个 Foo。

我想要一个 Factory,它可以创建包含 Bar 的 Foo。最理想的情况是这个 Bar 是通过 :bar 工厂创建的,并且遵循用于创建 Foo 的 build 策略(create/build)。

我知道我可以调用 :bar 工厂,然后从新 Bar 中获取 Foo 引用。但我想避免这种方式;在我的测试案例中,重要的对象是 Foo;调用 Bar 工厂似乎有点绕弯子。此外,我可以看到需要一个具有多个 Bars 的 Foo。

在 factory_girl 中是否有可能实现这个功能?如何定义父对象中的这种关系?

5个回答

51

Factory.after_hooks 似乎是唯一成功实现此操作的方法。我已经想出了一种方法,在不重复代码的情况下保持构建策略:

Factory.define :foo do |f|
  f.name "A Foo"
  f.after(:build) { |foo|
    foo.bars << Factory.build(:bar, :foo => foo)
  }
  f.after(:create) { |foo|
    foo.bars.each { |bar| bar.save! }
  }
end

文档中指出,如果使用:create构建策略,则会在调用after_create之前调用after_build。 如果使用:build,则仅调用after_build,所有人都很高兴。

我还创建了一个抽象的通用版本在这个 gist 中,以保持DRY(不要重复你自己)。


2
很难相信这是最好的方法,但似乎确实如此。点赞了这个答案。 - spier
你如何使用你的Gist版本?最好的方法是将其整合到Rails应用程序中? - joseph.hainline
此处所述,使用<<添加关联会使更改持久化。为了避免这种情况,应该将after(:build)设置为foo.association(:bars).add_to_target(Factory.build(:bar, :foo => foo)) - stevenspiel

4

您可以双向使用association方法:

Factory.define :foo do |f|
  # ...
  f.association :bar
end

如果那样行不通,你可以使用回调函数手动关联它们。以下是我其中一个应用程序的示例:

Factory.define :live_raid do |raid|
end

Factory.define :live_raid_with_attendee, :parent => :live_raid do |raid|
  raid.after_create { |r| Factory(:live_attendee, :live_raid => r) }
end

1
第一种语法在我的代码中导致堆栈溢出/无限递归。第二种语法运行得非常好,只是它不遵守构建策略。 - Craig Walker

3

当调用父对象上的build时,FactoryGirl 4.3.0会在关联对象上调用save!,我认为这不是正确的行为。

在查看了FactoryGirl代码后,现在在工厂中将strategy: :build添加到关联定义中似乎可以创建我的关联对象而不调用save!


3

2

使用factory_girl-4.5.0,在一个父对象工厂中创建n个子对象。

FactoryGirl.define do
  factory :foo do
    name "test"        

    after(:build) do |instance|
      n.times { instance.bars << FactoryGirl.create(:bar) }          
    end
  end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接