Как настроить фабрику в FactoryGirl с ассоциацией has_many

89

Может ли кто-нибудь сказать мне, если я просто неправильно настроен?

У меня есть следующие модели с ассоциациями 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 rspec для :listingфабрики:

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

Вот завод (: список)

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 это просто неразумно создавать таким образом фабричные объекты, или что мне не хватает? Большое спасибо за любой вклад!

Тонис
источник

Ответы:

57

Создание такого рода ассоциаций требует использования обратных вызовов FactoryGirl.

Здесь можно найти идеальный набор примеров.

https://oughttbot.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
Винфред
источник
вы в конечном итоге использовали Association: features, [: factory =>: feature]?
davidtingsu 03
109

Кроме того, вы можете использовать блок и пропустить associationключевое слово. Это позволяет создавать объекты без сохранения в базе данных (в противном случае ассоциация has_many сохранит ваши записи в db, даже если вы используете buildфункцию вместо create).

FactoryGirl.define do
  factory :listing_with_features, :parent => :listing do |listing|
    features { build_list :feature, 3 }
  end
end
JellicleCat
источник
5
Это кошачье мяуканье. Возможность обоих buildи createделает его наиболее универсальным узором. Затем используйте эту настраиваемую стратегию сборки FG gist.github.com/Bartuz/74ee5834a36803d712b7, чтобы post nested_attributes_forпри тестировании действий контроллера этоaccepts_nested_attributes_for
Крис Бек,
4
проголосовали за, гораздо более читабельный и универсальный, чем принятый ответ IMO
m_x
1
Начиная с FactoryBot 5, associationключевое слово использует одну и ту же стратегию сборки для родительского и дочернего. Таким образом, он может создавать объекты без сохранения в базе данных.
Ник
27

Вы можете использовать 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.oughttbot.com/remove-duplication-with-factorygirls-traits

Ehoffmann
источник
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
Дэйв Саг
источник
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)

Надеюсь, это поможет.

Рии
источник