Как создать ассоциации has_and_belongs_to_many в Factory Girl


120

Учитывая следующие

class User < ActiveRecord::Base
  has_and_belongs_to_many :companies
end

class Company < ActiveRecord::Base
  has_and_belongs_to_many :users
end

как вы определяете фабрики для компаний и пользователей, включая двунаправленную ассоциацию? Вот моя попытка

Factory.define :company do |f|
  f.users{ |users| [users.association :company]}
end

Factory.define :user do |f|
  f.companies{ |companies| [companies.association :user]}
end

теперь я пытаюсь

Factory :user

Возможно, неудивительно, что это приводит к бесконечному циклу, поскольку фабрики рекурсивно используют друг друга для определения себя.

Что еще более удивительно, я нигде не нашел упоминания о том, как это сделать, есть ли шаблон для определения необходимых фабрик или я делаю что-то в корне неправильно?

Ответы:


132

Вот решение, которое мне подходит.

FactoryGirl.define do

  factory :company do
    #company attributes
  end

  factory :user do
   companies {[FactoryGirl.create(:company)]}
   #user attributes
  end

end

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

company = FactoryGirl.create(:company, #{company attributes})
user = FactoryGirl.create(:user, :companies => [company])

Надеюсь, это будет кому-то полезно.


4
Спасибо, самое удачное из всех решений.
Mik

Спасибо. Это устранило мою проблему после нескольких часов разочарования.
Тони Бенинат

Это работает для меня только тогда, когда все фабрики находятся в одном файле, что совершенно нежелательно. Поэтому решение, упомянутое ниже @opsb, кажется лучше.
spier 02 авг.13,

40

С тех пор Factorygirl была обновлена ​​и теперь включает обратные вызовы для решения этой проблемы. Взгляните на http://robots.oughttbot.com/post/254496652/aint-no-calla-back-girl для получения дополнительной информации.


37
Ссылка на самом деле не говорит, как обращаться с has_and_belongs_to_many ... Я не понимаю, как это сделать ...
dmonopoly

3
Синтаксис обратного вызова теперь изменен на: after(:create)вместо after_createфабричной девушки, как упоминалось здесь: stackoverflow.com/questions/15003968/…
Майкл Ягудаев 05

22

На мой взгляд, просто создайте две разные фабрики, например:

 Factory.define: user,: class => User do | u |
  # Обычная инициализация атрибутов
 конец

 Factory.define: company,: class => Компания do | u |
  # Обычная инициализация атрибутов
 конец

Когда вы пишете тестовые примеры для пользователя, просто напишите так

 Завод (: пользователь,: компании => [Завод (: компания)])

Надеюсь, это сработает.


2
Спасибо, это единственный пример, с которым я смог работать. Заводская девушка - большая головная боль для habtm.
jspooner 01

Это больше не работает с последними версиями FactoryGirl (я думаю, что Rails 3)
Раф

9

Я не смог найти примера для вышеупомянутого случая на предоставленном сайте. (Только 1: N и полиморфные ассоциации, но без habtm). У меня был похожий случай, и мой код выглядит так:

Factory.define :user do |user|
 user.name "Foo Bar"
 user.after_create { |u| Factory(:company, :users => [u]) }
end

Factory.define :company do |c|
 c.name "Acme"
end

3
что, если есть проверка ненулевого количества пользователей?
dfens 07

5

Что сработало для меня, так это установка ассоциации при использовании factory. Используя ваш пример:

user = Factory(:user)
company = Factory(:company)

company.users << user 
company.save! 

4

Нашел этот способ красивым и многословным:

FactoryGirl.define do
  factory :foo do
    name "Foo" 
  end

  factory :bar do
    name "Bar"
    foos { |a| [a.association(:foo)] }
  end
end

1
foos { |a| [a.association(:foo)] }мне очень помогает! Спасибо!
monteirobrena

3
  factory :company_with_users, parent: :company do

    ignore do
      users_count 20
    end

    after_create do |company, evaluator|
      FactoryGirl.create_list(:user, evaluator.users_count, users: [user])
    end

  end

Предупреждение: измените пользователей: [user] на: users => [user] для ruby ​​1.8.x


4
Разве это не должно быть after_create { |company, evaluator| FactoryGirl.create_list(:user, evaluator.users_count, companies: [company]) }:?
Raf

0

Прежде всего, я настоятельно рекомендую вам использовать has_many: through вместо habtm (подробнее об этом здесь ), так что вы получите что-то вроде:

Employment belongs_to :users
Employment belongs_to :companies

User has_many :employments
User has_many :companies, :through => :employments 

Company has_many :employments
Company has_many :users, :through => :employments

После этого у вас будет ассоциация has_many с обеих сторон, и вы сможете назначить их в factory_girl так, как вы это сделали.


3
Это не должно быть Employment belongs_to :userи Employment belongs_to :companyс моделью присоединения , соединяющей одну компанию с одним пользователем?
Дэниел Бердсли,

5
Мой вывод из краткого прочтения сообщения, о котором вы упомянули, заключается в том, что от вашего варианта использования зависит, выбрать ли habtm или has_many: through. Настоящего «победителя» нет.
auralbee 03

Что ж, единственные накладные расходы при использовании hmt - это то, что вам нужно указать идентификатор в сквозной таблице. Прямо сейчас я не могу представить себе ситуацию, когда это могло вызвать какие-либо проблемы. Я не говорю, что habtm бесполезен, просто в 99% случаев имеет смысл использовать hmt (из-за его преимуществ).
Милан Новота 08

6
-1, потому что у HMT больше «преимуществ», означает, что вы должны использовать его, только если вам НУЖНЫ эти преимущества. Меня раздражает, потому что сейчас я работаю над проектом, в котором разработчик использовал HMT в нескольких случаях, когда HABTM было бы достаточно. Поэтому кодовая база больше, сложнее, менее интуитивно понятна и из-за этого производит более медленные соединения SQL. Итак, используйте HABTM, когда можете, а затем, когда вам НЕОБХОДИМО создать отдельную модель соединения для хранения дополнительной информации о каждой ассоциации, только затем используйте HMT.
sbeam 05

0

Обновление для Rails 5:

Вместо использования has_and_belongs_to_manyассоциации вы должны рассмотреть: has_many :throughассоциацию.

Пользовательская фабрика для этой ассоциации выглядит так:

FactoryBot.define do
  factory :user do
    # user attributes

    factory :user_with_companies do
      transient do
        companies_count 10 # default number
      end

      after(:create) do |user, evaluator|
         create_list(:companies, evaluator.companies_count, user: user)
      end
    end
  end
end

Аналогичным образом можно создать фабрику компании.

Как только обе фабрики настроены, вы можете создать user_with_companiesфабрику с помощью companies_count option. Здесь вы можете указать, к скольким компаниям принадлежит пользователь:create(:user_with_companies, companies_count: 15)

Вы можете найти подробное объяснение ассоциаций фабричных девушек здесь .


0

Для HABTM я использовал черты характера и обратные вызовы .

Допустим, у вас есть следующие модели:

class Catalog < ApplicationRecord
  has_and_belongs_to_many :courses
  
end
class Course < ApplicationRecord
  
end

Вы можете определить Factory выше :

FactoryBot.define do
  factory :catalog do
    description "Catalog description"
    

    trait :with_courses do
      after :create do |catalog|
        courses = FactoryBot.create_list :course, 2

        catalog.courses << courses
        catalog.save
      end
    end
  end
end

-1

Вы можете определить новую фабрику и использовать обратный вызов after (: create) для создания списка ассоциаций. Посмотрим, как это сделать на этом примере:

FactoryBot.define do

  # user factory without associated companies
  factory :user do
    # user attributes

    factory :user_with_companies do
      transient do
        companies_count 10
      end

      after(:create) do |user, evaluator|
        create_list(:companies, evaluator.companies_count, user: user)
      end
    end
  end
end

Атрибут companies_count является временным и доступен в атрибутах фабрики и в обратном вызове через оценщик. Теперь вы можете создать пользователя с компаниями с возможностью указать, сколько компаний вы хотите:

create(:user_with_companies).companies.length # 10
create(:user_with_companies, companies_count: 15).companies.length # 15
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.