Comment créer des associations has_and_belongs_to_many dans Factory Girl


120

Compte tenu de ce qui suit

class User < ActiveRecord::Base
  has_and_belongs_to_many :companies
end

class Company < ActiveRecord::Base
  has_and_belongs_to_many :users
end

comment définissez-vous les usines pour les entreprises et les utilisateurs, y compris l'association bidirectionnelle? Voici ma tentative

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

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

maintenant j'essaye

Factory :user

Sans surprise, cela se traduit peut-être par une boucle infinie, car les usines s'utilisent réciproquement pour se définir.

Plus surprenant, je n'ai trouvé aucune mention de la façon de faire cela nulle part, y a-t-il un modèle pour définir les usines nécessaires ou je fais quelque chose de fondamentalement mal?

Réponses:


132

Voici la solution qui fonctionne pour moi.

FactoryGirl.define do

  factory :company do
    #company attributes
  end

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

end

si vous avez besoin d'une entreprise spécifique, vous pouvez utiliser l'usine de cette façon

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

J'espère que cela sera utile pour quelqu'un.


4
Merci, la plus soignée de toutes les solutions.
Mik

Je vous remercie. Cela a résolu mon problème après des heures de frustration.
Tony Beninate

Cela ne fonctionne pour moi que lorsque toutes les usines sont dans un seul fichier, ce qui est tout à fait indésirable. Par conséquent, la solution mentionnée par @opsb ci-dessous semble être meilleure.
spier

40

Factorygirl a depuis été mis à jour et inclut désormais des rappels pour résoudre ce problème. Jetez un œil à http://robots.thoughtbot.com/post/254496652/aint-no-calla-back-girl pour plus d'informations.


37
Le lien ne dit pas vraiment comment gérer has_and_belongs_to_many ... Je ne vois pas comment faire cela ...
dmonopoly

3
La syntaxe de rappel a maintenant été modifiée pour: after(:create)au lieu de after_createfille d'usine comme mentionné ici: stackoverflow.com/questions/15003968/...
Michael Yagudaev

22

À mon avis, il suffit de créer deux usines différentes comme:

 Factory.define: utilisateur,: class => Utilisateur do | u |
  # Initialisation des attributs normaux
 fin

 Factory.define: société,: class => Société do | u |
  # Initialisation des attributs normaux
 fin

Lorsque vous écrivez les cas de test pour l'utilisateur, écrivez simplement comme ceci

 Usine (: utilisateur,: entreprises => [Usine (: entreprise)])

J'espère que cela fonctionnera.


2
Merci, c'est le seul exemple que j'ai pu obtenir. Factory Girl est un gros casse-tête pour habtm.
jspooner

Cela ne fonctionne plus avec les versions récentes de FactoryGirl (je pense à Rails 3)
Raf

9

Je n'ai pas pu trouver d'exemple pour le cas mentionné ci-dessus sur le site Web fourni. (Seulement 1: N et associations polymorphes, mais pas d'habtm). J'ai eu un cas similaire et mon code ressemble à ceci:

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
et s'il y a validation du nombre d'utilisateurs différent de zéro?
dfens

5

Ce qui a fonctionné pour moi, c'est la création de l'association lors de l'utilisation de l'usine. En utilisant votre exemple:

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

company.users << user 
company.save! 

4

Trouvé de cette façon agréable et verbeux:

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)] }m'aide beaucoup! Je vous remercie!
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

Avertissement: Remplacez les utilisateurs: [utilisateur] par: utilisateurs => [utilisateur] pour ruby ​​1.8.x


4
Ne devrait-il pas être after_create { |company, evaluator| FactoryGirl.create_list(:user, evaluator.users_count, companies: [company]) }:?
Raf

0

Tout d'abord, je vous encourage fortement à utiliser has_many: through au lieu de habtm (plus à ce sujet ici ), vous vous retrouverez donc avec quelque chose comme:

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

Après cela, vous aurez une association has_many des deux côtés et pourrez leur attribuer dans factory_girl comme vous l'avez fait.


3
Cela ne devrait-il pas être le cas Employment belongs_to :useret Employment belongs_to :companyavec le modèle de jointure connectant une entreprise à un utilisateur?
Daniel Beardsley

5
Ma conclusion d'une lecture rapide de l'article que vous avez mentionné est que cela dépend de votre cas d'utilisation de choisir habtm ou has_many: through. Il n'y a pas de véritable «gagnant».
auralbee

Eh bien, la seule surcharge lors de l'utilisation de hmt est que vous devez avoir l'ID défini sur la table through. À l'heure actuelle, je ne peux pas imaginer une situation où cela pourrait causer un problème. Je ne dis pas que habtm ne sert à rien, juste que dans 99% des cas d'utilisation, il est plus logique d'utiliser hmt (en raison de ses avantages).
Milan Novota

6
-1, juste parce que HMT a plus «d'avantages» signifie que vous devez l'utiliser si vous avez BESOIN de ces avantages. Pet beaute, car je travaille actuellement sur un projet où le développeur a utilisé HMT dans plusieurs cas où HABTM aurait suffi. La base de code est donc plus grande, plus complexe, moins intuitive et produit des jointures SQL plus lentes à cause de cela. Donc, utilisez HABTM lorsque vous le pouvez, puis lorsque vous DEVEZ créer un modèle de jointure distinct pour stocker des informations supplémentaires sur chaque association, utilisez HMT uniquement.
sbeam

0

Mise à jour pour Rails 5:

Au lieu d'utiliser l' has_and_belongs_to_manyassociation, vous devriez considérer: has_many :throughassociation.

La fabrique d'utilisateurs pour cette association ressemble à ceci:

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

Vous pouvez créer l'usine d'entreprise de la même manière.

Une fois les deux usines définies, vous pouvez créer une user_with_companiesusine avec companies_count option. Ici, vous pouvez spécifier le nombre d'entreprises auxquelles appartient l'utilisateur:create(:user_with_companies, companies_count: 15)

Vous pouvez trouver des explications détaillées sur les associations de filles d'usine ici .


0

Pour HABTM, j'ai utilisé des traits et des rappels .

Supposons que vous ayez les modèles suivants:

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

Vous pouvez définir l'usine ci - dessus :

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

Vous pouvez définir une nouvelle fabrique et utiliser après le rappel (: create) pour créer une liste d'associations. Voyons comment le faire dans cet exemple:

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

L'attribut companies_count est un transitoire et disponible dans les attributs de la fabrique et dans le callback via l'évaluateur. Désormais, vous pouvez créer un utilisateur avec des entreprises avec la possibilité de spécifier le nombre d'entreprises que vous souhaitez:

create(:user_with_companies).companies.length # 10
create(:user_with_companies, companies_count: 15).companies.length # 15
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.