如何通过 has_many 关联在 FactoryBot 中设置工厂

Posted

技术标签:

【中文标题】如何通过 has_many 关联在 FactoryBot 中设置工厂【英文标题】:How to set up factory in FactoryBot with has_many association 【发布时间】:2011-10-21 06:07:08 【问题描述】:

谁能告诉我,如果我只是以错误的方式进行设置?

我有以下具有 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。这是我对:listing 工厂的基本 rspec rspec 健全性检查:

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

这里是工厂(: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 #&lt;Feature&gt; 我认为这对我来说很有意义,因为 listing.features 返回一个数组。所以我把它改成了

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

我现在得到的错误是ArgumentError: Not registered: features 以这种方式生成工厂对象是不明智的,还是我错过了什么?非常感谢您的任何意见!

【问题讨论】:

【参考方案1】:

或者,您可以使用块并跳过association 关键字。这使得在不保存到数据库的情况下构建对象成为可能(否则,has_many 关联会将您的记录保存到数据库,即使您使用build 函数而不是create)。

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

【讨论】:

这是猫的叫声。 buildcreate 的能力使它成为最通用的模式。然后在测试 accepts_nested_attributes_for 的控制器操作时使用此自定义 FG 构建策略 gist.github.com/Bartuz/74ee5834a36803d712b7 到 post nested_attributes_for 赞成,比接受的答案 IMO 更具可读性和通用性 从 FactoryBot 5 开始,association 关键字对父级和子级使用相同的构建策略。因此,它可以构建对象而不保存到数据库。【参考方案2】:

创建这些类型的关联需要使用 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] 吗?【参考方案3】:

你可以使用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

【讨论】:

【参考方案4】:

我尝试了几种不同的方法,这是对我来说最可靠的方法(适用于您的情况)

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 起,关联保留了构建策略。关联是解决这个问题的最佳方式,docs have good examples for it:

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

【讨论】:

【参考方案6】:

我的设置如下:

# 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)

希望对您有所帮助。

【讨论】:

以上是关于如何通过 has_many 关联在 FactoryBot 中设置工厂的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 has_many 关联在 FactoryBot 中设置工厂

使用 has_many 时如何创建关联模型的实例:通过关联

如何通过“has_many”关联获取数据?

Rails has_many,如何实现嵌套关联?

如何通过rails中的关联定义has_many

如何使用带有 has_many 的 PaperTrail 版本控制:通过在 Rails 4 中实现关联