如何在 FactoryBot 中使用 has_many 关联设置工厂

谁能告诉我,我是不是走错了方向?

我有以下模型,它们有 _ 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 _ ails 1.1.0和 rspec。下面是我对 :listing工厂的基本规格检查:

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

这里是 Factory (: 列表)

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

错误消息是 因为 listing.features返回一个数组,所以我认为 undefined method 'each' for #<Feature>对我来说是有意义的。所以我改成了

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

我现在得到的错误是 ArgumentError: Not registered: features这样生成工厂对象是不明智的吗,还是我错过了什么?非常感谢所有的投入!

89654 次浏览

Creating these kinds of associations requires using FactoryGirl's callbacks.

A perfect set of examples can be found here.

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

To bring it home to your example.

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

Alternatively, you can use a block and skip the association keyword. This makes it possible to build objects without saving to the database (otherwise, a has_many association will save your records to the db, even if you use the build function instead of create).

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

I tried a few different approaches and this is the one that worked most reliably for me (adapted to your case)

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

Here is how I set mine up:

# 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

And then in your tests you can do:

@preference_set = FactoryGirl.create(:preference_set_with_preferences)

Hope that helps.

You could use trait:

FactoryGirl.define do
factory :listing do
...


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

With callback, if you need DB creation:

...


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

Use in your specs like this:

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

This will remove duplication in your factories and be more reusable.

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

Since FactoryBot v5, associations preserve build strategy. Associations are the best way to solve this and the 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

Or with control over the count:

    transient do
posts_count { 5 }
end


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

Similar to @thisismydesign, however it created an additional post on my end (FactoryBot v6.2).

To avoid this situation, I've added keyword instance as below:

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