Cómo crear asociaciones has_and_belongs_to_many en Factory Girl

120

Dado lo siguiente

class User < ActiveRecord::Base
  has_and_belongs_to_many :companies
end

class Company < ActiveRecord::Base
  has_and_belongs_to_many :users
end

¿Cómo se definen las fábricas para empresas y usuarios incluida la asociación bidireccional? Este es mi intento

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

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

ahora lo intento

Factory :user

Quizás, como era de esperar, esto da como resultado un bucle infinito, ya que las fábricas se usan recursivamente para definirse a sí mismas.

Más sorprendentemente, no he encontrado una mención de cómo hacer esto en ninguna parte, ¿hay un patrón para definir las fábricas necesarias o estoy haciendo algo fundamentalmente mal?

opsb
fuente

Respuestas:

132

Aquí está la solución que me funciona.

FactoryGirl.define do

  factory :company do
    #company attributes
  end

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

end

Si necesita una empresa específica, puede usar la fábrica de esta manera.

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

Espero que esto sea de ayuda para alguien.

Suborx
fuente
4
Gracias, la más elegante de todas las soluciones.
Mik
Gracias. Esto solucionó mi problema después de horas de frustración.
Tony Beninate
Esto solo funciona para mí cuando todas las fábricas están en un archivo, lo cual es bastante indeseable. Por lo tanto, la solución mencionada por @opsb a continuación parece ser mejor.
spier
40

Desde entonces, Factorygirl se ha actualizado y ahora incluye devoluciones de llamada para resolver este problema. Eche un vistazo a http://robots.thoughtbot.com/post/254496652/aint-no-calla-back-girl para obtener más información.

opsb
fuente
37
El enlace en realidad no dice cómo manejar has_and_belongs_to_many ... No veo cómo hacer esto ...
dmonopoly
3
La sintaxis de devolución de llamada ahora se ha cambiado a: en after(:create)lugar de after_createen factory girl como se menciona aquí: stackoverflow.com/questions/15003968/…
Michael Yagudaev
22

En mi opinión, solo crea dos fábricas diferentes como:

 Factory.define: usuario,: ​​clase => Usuario hacer | u |
  # Solo inicialización de atributos normales
 final

 Factory.define: empresa,: clase => Empresa hacer | u |
  # Solo inicialización de atributos normales
 final

Cuando escriba los casos de prueba para el usuario, simplemente escriba así

 Fábrica (: usuario,: ​​empresas => [Fábrica (: empresa)])

Espero que funcione.

Ashish
fuente
2
Gracias, este es el único ejemplo que pude conseguir. Factory girl es un gran dolor de cabeza para habtm.
jspooner
Esto ya no funciona con versiones recientes de FactoryGirl (estoy pensando en Rails 3)
Raf
9

No pude encontrar un ejemplo para el caso mencionado anteriormente en el sitio web proporcionado. (Solo 1: N y asociaciones polimórficas, pero no habtm). Tuve un caso similar y mi código se ve así:

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
auralbee
fuente
3
¿Qué pasa si hay una validación del recuento de usuarios distinto de cero?
dfens
5

Lo que funcionó para mí fue establecer la asociación al usar la fábrica. Usando tu ejemplo:

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

company.users << user 
company.save! 
Larry Kooper
fuente
4

Encontrado de esta manera agradable y detallado:

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

  factory :bar do
    name "Bar"
    foos { |a| [a.association(:foo)] }
  end
end
pasha.zhukov
fuente
1
foos { |a| [a.association(:foo)] }me ayuda mucho! ¡Gracias!
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

Advertencia: cambie usuarios: [usuario] a: usuarios => [usuario] para ruby ​​1.8.x

Artur79
fuente
4
¿No debería ser after_create { |company, evaluator| FactoryGirl.create_list(:user, evaluator.users_count, companies: [company]) }:?
Raf
0

En primer lugar, te recomiendo encarecidamente que uses has_many: through en lugar de habtm (más sobre esto aquí ), por lo que terminarás con algo como:

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

Después de esto, tendrá la asociación has_many en ambos lados y podrá asignarles en factory_girl de la forma en que lo hizo.

Milán Novota
fuente
3
¿No debería ser así Employment belongs_to :usery Employment belongs_to :companycon el modelo de unión que conecta una empresa con un usuario?
Daniel Beardsley
5
Mi conclusión de una lectura rápida de la publicación que mencionaste es que depende de tu caso de uso si elegir habtm o has_many: through. No hay un verdadero "ganador".
auralbee
Bueno, la única sobrecarga cuando se usa hmt es que debe tener una identificación definida en la tabla. En este momento, no puedo imaginar una situación en la que eso pueda causar algún problema. No digo que habtm sea inútil, solo que en el 99% de los casos de uso tiene más sentido usar hmt (debido a sus ventajas).
Milán Novota
6
-1, solo porque HMT tiene más 'ventajas' solo significa que debe usarlo si NECESITA esas ventajas. Me molesta, porque ahora estoy trabajando en un proyecto en el que el desarrollador usó HMT en varios casos en los que HAB TM hubiera sido suficiente. Por lo tanto, el código base es más grande, más complejo, menos intuitivo y produce uniones SQL más lentas debido a ello. Por lo tanto, use HAB TM cuando pueda, luego, cuando NECESITA crear un modelo de unión separado para almacenar información adicional sobre cada asociación, solo luego use HMT.
sbeam
0

Actualización para Rails 5:

En lugar de utilizar la has_and_belongs_to_manyasociación, debería considerar: has_many :throughasociación.

La fábrica de usuarios para esta asociación se ve así:

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

Puede crear la fábrica de la empresa de forma similar.

Una vez configuradas ambas fábricas, puede crear una user_with_companiesfábrica con companies_count option. Aquí puede especificar a cuántas empresas pertenece el usuario:create(:user_with_companies, companies_count: 15)

Puede encontrar una explicación detallada sobre las asociaciones de chicas de fábrica aquí .

Nesha Zoric
fuente
0

Para HABTM utilicé rasgos y devoluciones de llamada .

Digamos que tiene los siguientes modelos:

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

Puede definir la Fábrica arriba :

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
lucasarruda
fuente
-1

Puede definir una nueva fábrica y usarla después de (: crear) devolución de llamada para crear una lista de asociaciones. Veamos cómo hacerlo en este ejemplo:

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

El atributo companies_count es transitorio y está disponible en los atributos de la fábrica y en la devolución de llamada a través del evaluador. Ahora, puede crear un usuario con empresas con la opción de especificar cuántas empresas desea:

create(:user_with_companies).companies.length # 10
create(:user_with_companies, companies_count: 15).companies.length # 15
Aleksandar M.
fuente