如何在FactoryBot中设置具有has_many关联的工厂

103

请问我是否设置方式不正确?

我有以下模型,它们具有has_many.through关联:

class Listing < ActiveRecord::Base
  attr_accessible ... 

  has_many :listing_features
  has_many :features, :through => :listing_features

  validates_presence_of ...
  ...  
end


class Feature < ActiveRecord::Base
  attr_accessible ...

  validates_presence_of ...
  validates_uniqueness_of ...

  has_many :listing_features
  has_many :listings, :through => :listing_features
end


class ListingFeature < ActiveRecord::Base
  attr_accessible :feature_id, :listing_id

  belongs_to :feature  
  belongs_to :listing
end

我正在使用Rails 3.1.rc4,FactoryGirl 2.0.2,factory_girl_rails 1.1.0和rspec。这是我的基本rspec健全性检查,关于:listing工厂:

it "creates a valid listing from factory" do
  Factory(:listing).should be_valid
end

这里是 Factory(:listing)。

FactoryGirl.define do
  factory :listing do
    headline    'headline'
    home_desc   'this is the home description'
    association :user, :factory => :user
    association :layout, :factory => :layout
    association :features, :factory => :feature
  end
end

:listing_feature:feature工厂的设置类似。
如果注释掉association :features这一行,则所有测试都会通过。
当它被取消注释时,

association :features, :factory => :feature

错误消息是 undefined method 'each' for #<Feature>,我认为这很有道理,因为listing.features返回的是数组。所以我将其更改为

association :features, [:factory => :feature]

现在我得到的错误是ArgumentError: Not registered: features,只是用这种方式生成工厂对象不明智吗?或者我漏掉了什么?非常感谢您所有的帮助!

7个回答

116

或者,您可以使用块并跳过association关键字。这使得可以构建对象而无需保存到数据库(否则,即使您使用build函数而不是create函数,has_many关联也会将记录保存到数据库中)。

FactoryGirl.define do
  factory :listing_with_features, :parent => :listing do |listing|
    features { build_list :feature, 3 }
  end
end

5
这是非常棒的。能够同时“构建”和“创建”使它成为最通用的模式。然后,请使用此自定义FG构建策略https://gist.github.com/Bartuz/74ee5834a36803d712b7,在测试控制器操作时使用`accepts_nested_attributes_for`,以`post nested_attributes_for`。 - Chris Beck
4
在我看来,这个回答比被接受的回答更易读和灵活,我赞同它。"upvoted"翻译成“点赞”。 - m_x
1
从FactoryBot 5开始,association关键字使用相同的构建策略来构建父对象和子对象。因此,它可以构建对象而无需保存到数据库中。 - Nick

59

创建这些类型的关联需要使用FactoryGirl的回调函数。

可以在这里找到一组完美的示例。

https://thoughtbot.com/blog/aint-no-calla-back-girl

让我们将其应用到您的示例中。

Factory.define :listing_with_features, :parent => :listing do |listing|
  listing.after_create { |l| Factory(:feature, :listing => l)  }
  #or some for loop to generate X features
end

你最终使用了关联:features,[:factory => :feature]吗? - davidtingsu

42
你可以使用trait
FactoryGirl.define do
  factory :listing do
    ...

    trait :with_features do
      features { build_list :feature, 3 }
    end
  end
end

使用 callback,如果您需要创建数据库:

...

trait :with_features do
  after(:create) do |listing|
    create_list(:feature, 3, listing: listing)
  end
end

像这样在您的规格中使用:

let(:listing) { create(:listing, :with_features) }

这将消除您的工厂中的重复,并使其更具可重用性。

https://robots.thoughtbot.com/remove-duplication-with-factorygirls-traits


20

我尝试了几种不同的方法,这是对我来说最可靠的一种(根据您的情况进行了调整)

FactoryGirl.define do
  factory :user do
    # some details
  end

  factory :layout do
    # some details
  end

  factory :feature do
    # some details
  end

  factory :listing do
    headline    'headline'
    home_desc   'this is the home description'
    association :user, factory: :user
    association :layout, factory: :layout
    after(:create) do |liztng|
      FactoryGirl.create_list(:feature, 1, listing: liztng)
    end
  end
end

5

自FactoryBot v5以来,关联关系保留了构建策略。 关联关系是解决这个问题的最佳方法,文档中有很好的示例

FactoryBot.define do
  factory :post do
    title { "Through the Looking Glass" }
    user
  end

  factory :user do
    name { "Taylor Kim" }

    factory :user_with_posts do
      posts { [association(:post)] }
    end
  end
end

或者通过控制计数来实现:

    transient do
      posts_count { 5 }
    end

    posts do
      Array.new(posts_count) { association(:post) }
    end

0

与@thisismydesign类似,但它在我的端口上创建了一个额外的post(FactoryBot v6.2)。

为避免这种情况,我已添加关键字instance如下:

FactoryBot.define do
  factory :post do
    title { "Through the Looking Glass" }
    user
  end

  factory :user do
    name { "Taylor Kim" }

    factory :user_with_posts do
      posts { [association(:post, user: instance)] }
    end
  end
end

0

这是我设置我的方式:

# Model 1 PreferenceSet
class PreferenceSet < ActiveRecord::Base
  belongs_to :user
  has_many :preferences, dependent: :destroy
end

#Model 2 Preference

class Preference < ActiveRecord::Base    
  belongs_to :preference_set
end



# factories/preference_set.rb

FactoryGirl.define do
  factory :preference_set do
    user factory: :user
    filter_name "market, filter_structure"

    factory :preference_set_with_preferences do
      after(:create) do |preference|
        create(:preference, preference_set: preference)
        create(:filter_structure_preference, preference_set: preference)
      end
    end
  end

end

# factories/preference.rb

FactoryGirl.define do
  factory :preference do |p|
    filter_name "market"
    filter_value "12"
  end

  factory :filter_structure_preference, parent: :preference do
    filter_name "structure"
    filter_value "7"
  end
end

然后在你的测试中可以这样做:

@preference_set = FactoryGirl.create(:preference_set_with_preferences)

希望这有所帮助。

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