125

Given the following

class User < ActiveRecord::Base
  has_and_belongs_to_many :companies
end

class Company < ActiveRecord::Base
  has_and_belongs_to_many :users
end

how do you define factories for companies and users including the bidirectional association? Here's my attempt

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

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

now I try

Factory :user

Perhaps unsurprisingly this results in an infinite loop as the factories recursively use each other to define themselves.

More surprisingly I haven't found a mention of how to do this anywhere, is there a pattern for defining the necessary factories or I am doing something fundamentally wrong?

Ryan Bigg
  • 106,965
  • 23
  • 235
  • 261
opsb
  • 29,325
  • 19
  • 89
  • 99

11 Answers11

136

Here is the solution that works for me.

FactoryGirl.define do

  factory :company do
    #company attributes
  end

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

end

if you will need specific company you can use factory this way

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

Hope this will be helpful for somebody.

Suborx
  • 3,647
  • 2
  • 19
  • 30
40

Factorygirl has since been updated and now includes callbacks to solve this problem. Take a look at http://robots.thoughtbot.com/post/254496652/aint-no-calla-back-girl for more info.

opsb
  • 29,325
  • 19
  • 89
  • 99
  • 38
    The link doesn't actually say how to handle has_and_belongs_to_many... I don't see how to do this... – dmonopoly Jul 25 '11 at 21:16
  • 3
    Callback syntax has now been changed to: `after(:create)` instead of `after_create` in factory girl as mentioned here: http://stackoverflow.com/questions/15003968/undefined-method-after-create-with-factorygirl – Michael Yagudaev Mar 05 '14 at 07:12
22

In my opinion, Just create two different factories like:

 Factory.define :user, :class => User do |u|
  # Just normal attributes initialization
 end

 Factory.define :company, :class => Company do |u|
  # Just normal attributes initialization
 end

When you write the test-cases for user then just write like this

 Factory(:user, :companies => [Factory(:company)])

Hope it will work.

Ashish
  • 5,723
  • 2
  • 24
  • 25
9

I couldn´t find an example for the above mentioned case on the provided website. (Only 1:N and polymorphic assocations, but no habtm). I had a similar case and my code looks like this:

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
  • 8,741
  • 4
  • 29
  • 36
5

What worked for me was setting the association when using the factory. Using your example:

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

company.users << user 
company.save! 
noli
  • 15,927
  • 8
  • 46
  • 62
Larry Kooper
  • 71
  • 1
  • 6
4

Found this way nice and verbose:

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

  factory :bar do
    name "Bar"
    foos { |a| [a.association(:foo)] }
  end
end
pasha.zhukov
  • 1,272
  • 10
  • 9
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

Warning: Change users: [user] to :users => [user] for ruby 1.8.x

Artur79
  • 12,705
  • 1
  • 22
  • 22
  • 4
    Shouldn't it be : `after_create { |company, evaluator| FactoryGirl.create_list(:user, evaluator.users_count, companies: [company]) }` ? – Raf Nov 08 '12 at 14:56
1

For HABTM I used traits and callbacks.

Say you have the following models:

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

You can define the Factory above:

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
  • 1,462
  • 1
  • 25
  • 45
0

First of all I strongly encourage you to use has_many :through instead of habtm (more about this here), so you'll end up with something like:

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

After this you'll have has_many association on both sides and can assign to them in factory_girl in the way you did it.

noli
  • 15,927
  • 8
  • 46
  • 62
Milan Novota
  • 15,506
  • 7
  • 54
  • 62
  • 3
    Shouldn't that be `Employment belongs_to :user` and `Employment belongs_to :company` with the join model connecting one Company with one User? – Daniel Beardsley Jun 15 '10 at 11:00
  • 5
    My conclusion from a quick read through the post you mentioned is that it depends on your use case whether to choose habtm or has_many :through. There is no real "winner". – auralbee Mar 03 '11 at 17:02
  • Well, the only overhead when using hmt is that you have to have id defined on the through table. Right now, I can't imagine a situation when that could cause any problem. I don't say that habtm is of no use, just that in 99% use cases it makes more sense to use hmt (because of its advantages). – Milan Novota Mar 08 '11 at 21:49
  • 6
    -1, just because HMT has more 'advantages' only means you should use it if you NEED those advantages. Pet peeve, because I'm working on a project now where the developer used HMT in several cases where HABTM would have sufficed. The codebase is therefore larger, more complex, less intuitive and produces slower SQL joins because of it. So, use HABTM when you can, then when you NEED to create a separate join model to store extra info about each association, only then use HMT. – sbeam Jun 05 '12 at 02:05
0

Update for Rails 5:

Instead of using has_and_belongs_to_many association, you should consider: has_many :through association.

The user factory for this association looks like this:

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

You can create the company factory in a similar way.

Once both factories are set, you can create user_with_companies factory with companies_count option. Here you can specify how many companies the user belongs to: create(:user_with_companies, companies_count: 15)

You can find detailed explanation about factory girl associations here.

Nesha Zoric
  • 6,218
  • 42
  • 34
-1

You can define new factory and use after(:create) callback to create a list of associations. Let's see how to do it in this example:

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

Attribute companies_count is a transient and available in attributes of the factory and in the callback via the evaluator. Now, you can create a user with companies with the option to specify how many companies you want:

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