Como criar associações has_and_belongs_to_many no Factory girl


119

Dado o seguinte

class User < ActiveRecord::Base
  has_and_belongs_to_many :companies
end

class Company < ActiveRecord::Base
  has_and_belongs_to_many :users
end

como você define fábricas para empresas e usuários, incluindo a associação bidirecional? Aqui está a minha tentativa

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

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

agora eu tento

Factory :user

Talvez sem surpresa isso resulte em um loop infinito, pois as fábricas recursivamente se usam para se definir.

Surpreendentemente, não encontrei uma menção de como fazer isso em nenhum lugar; existe um padrão para definir as fábricas necessárias ou estou fazendo algo fundamentalmente errado?

Respostas:


132

Aqui está a solução que funciona para mim.

FactoryGirl.define do

  factory :company do
    #company attributes
  end

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

end

Se você precisar de uma empresa específica, poderá usar a fábrica dessa maneira

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

Espero que isso seja útil para alguém.


4
Obrigado, mais elegante de todas as soluções.
29412 Mik

Obrigado. Isso resolveu meu problema após horas de frustração.
Tony Beninate

Isso só funciona para mim quando todas as fábricas estão em um arquivo, o que é bastante indesejável. Portanto, a solução mencionada por @opsb abaixo parece ser melhor.
Spier #

40

O Factorygirl foi atualizado e agora inclui retornos de chamada para resolver esse problema. Dê uma olhada em http://robots.thoughtbot.com/post/254496652/aint-no-calla-back-girl para obter mais informações.


37
Na verdade, o link não diz como lidar com has_and_belongs_to_many ... Não vejo como fazer isso ...
dmonopoly

3
Sintaxe de retorno de chamada agora foi alterado para: after(:create)em vez de after_createem Factory Girl como mencionado aqui: stackoverflow.com/questions/15003968/...
Michael Yagudaev

22

Na minha opinião, basta criar duas fábricas diferentes, como:

 Factory.define: user,: class => User do | u |
  # Apenas inicialização normal de atributos
 fim

 Factory.define: company,: class => Company do | u |
  # Apenas inicialização normal de atributos
 fim

Quando você escreve os casos de teste para o usuário, basta escrever assim

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

Espero que funcione.


2
Obrigado, este é o único exemplo que eu poderia começar a trabalhar. A menina da fábrica é uma grande dor de cabeça para o habtm.
Jspooner

Isto já não funciona com versões recentes do FactoryGirl (estou pensando Rails 3)
Raf

9

Não encontrei um exemplo para o caso mencionado acima no site fornecido. (Apenas 1: N e associações polimórficas, mas não habtm). Eu tive um caso semelhante e meu código fica assim:

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
e se houver validação de contagem de usuários diferente de zero?
dfens

5

O que funcionou para mim foi definir a associação ao usar a fábrica. Usando seu exemplo:

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

company.users << user 
company.save! 

4

Achado desta maneira agradável e detalhado:

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)] }me ajuda muito! Obrigado!
31718

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

Aviso: Altere os usuários: [user] para: users => [user] para ruby ​​1.8.x


4
Não deveria ser after_create { |company, evaluator| FactoryGirl.create_list(:user, evaluator.users_count, companies: [company]) }:?
Raf

0

Antes de tudo, recomendo que você use has_many: through em vez de habtm (mais sobre isso aqui ), para que você termine com 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

Depois disso, você terá muitas associações de ambos os lados e poderá atribuir a elas em factory_girl da maneira que você fez.


3
Não deveria ser Employment belongs_to :usere Employment belongs_to :companycom o modelo de junção conectando uma empresa a um usuário?
Daniel Beardsley

5
Minha conclusão de uma rápida leitura da postagem que você mencionou é que depende do seu caso de uso escolher habtm ou has_many: through. Não existe um verdadeiro "vencedor".
precisa saber é

Bem, a única sobrecarga ao usar o hmt é que você precisa ter um ID definido na tabela de passagem. No momento, não consigo imaginar uma situação em que isso possa causar algum problema. Não digo que o habtm é inútil, apenas que em 99% dos casos de uso faz mais sentido usar o hmt (por causa de suas vantagens).
Milan Novota

6
-1, apenas porque o HMT tem mais 'vantagens' significa apenas que você deve usá-lo se precisar dessas vantagens. Pet peeve, porque agora estou trabalhando em um projeto em que o desenvolvedor usou o HMT em vários casos em que o HABTM seria suficiente. A base de código é, portanto, maior, mais complexa, menos intuitiva e produz junções SQL mais lentas por causa disso. Portanto, use o HABTM quando puder e, quando PRECISAR criar um modelo de junção separado para armazenar informações extras sobre cada associação, somente use o HMT.
sbeam

0

Atualização para o Rails 5:

Em vez de usar has_and_belongs_to_manyassociação, você deve considerar: has_many :throughassociação.

A fábrica do usuário para esta associação é assim:

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

Você pode criar a fábrica da empresa de maneira semelhante.

Depois que as duas fábricas estiverem definidas, você poderá criar uma user_with_companiesfábrica companies_count option. Aqui você pode especificar a quantas empresas o usuário pertence:create(:user_with_companies, companies_count: 15)

Você pode encontrar explicações detalhadas sobre as associações de garotas de fábrica aqui .


0

Para o HABTM, usei características e retornos de chamada .

Digamos que você tenha os seguintes modelos:

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

Você pode definir a fábrica acima :

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

Você pode definir uma nova fábrica e usar o retorno de chamada após (: criar) para criar uma lista de associações. Vamos ver como fazer isso neste exemplo:

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

O atributo companies_count é temporário e está disponível em atributos da fábrica e no retorno de chamada por meio do avaliador. Agora, você pode criar um usuário com empresas com a opção de especificar quantas empresas você deseja:

create(:user_with_companies).companies.length # 10
create(:user_with_companies, companies_count: 15).companies.length # 15
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.