Misha M
Misha M

Reputation: 11299

FactoryGirl complex association with multiple models

I'm trying to figure out how to write a factory that belongs to 2 different models that each should have the same parent model. Here's the contrived sample code:

class User < ActiveRecord::Base
  has_many :widgets
  has_many :suppliers

  attr_accessible :username
end

class Widget < ActiveRecord::Base
  belongs_to :user
  has_many :parts

  attr_accessible :name
end

class Supplier < ActiveRecord::Base
  belongs_to :user
  has_many :parts

  attr_accessible :name
end

class Part < ActiveRecord::Base
  belongs_to :supplier
  belongs_to :widget

  attr_accessible :name
end

Here's what I have so far:

factory :user do
  name 'foo'
end

factory :widget do
  association :user
  name 'widget'
end

factory :supplier do
  association :user
  name 'supplier'
end

factory :part do
  association :widget
  association :supplier
  name 'part'
end

The problem with this is that the part.widget.user != part.supplier.user and they have to be the same.

I've tried the following with no success:

factory :part do
  association :widget
  association :supplier, user: widget.user
  name 'part'
end

Any suggestions? Or do I have to modify it after I create the part?

Thank you

Upvotes: 6

Views: 2091

Answers (2)

br3nt
br3nt

Reputation: 9586

Another option is to use transient variables to allow an associated object to be passed in.

I generally use two variables:

  • A variable to hold the association to be used within the factory
  • A boolean variable to indicate whether or not to generate a default value for the association variable -- probably not needed in your specific case, but can be very useful

This is how it would look:

factory :part do
  transient do
    # this variable is so we can specify the user
    with_user { no_user ? nil : Factory.create(:user) }

    # this variable allows the user to be nil
    no_user false 
  end

  # The transient variable for_user can now be used to create the 
  # associations for this factory
  widget { Factory.create(:widget, :user => with_user) }
  supplier { Factory.create(:supplier, :user => with_user) }

  name 'part'
end

This can then be used in the following ways:

# use the default user
part = Factory.create :part
part.widget.user.should == part.supplier.user

# use a custom created user
user = Factory.create :user, :name => 'Custom user'
part = Factory.create :part, for_user: user
part.widget.user.should == user
part.supplier.user.should == user

# create a part without any user
# (again this probably isn't need in your specific case, but I have 
#  found it a useful pattern)
part = Factory.create :part, no_user: true
part.widget.user.should be_nil
part.supplier.user.should be_nil

Upvotes: 0

Chris Salzberg
Chris Salzberg

Reputation: 27374

I believe you could do this with a callback:

factory :part do
  association :widget
  association :supplier
  name 'part'
  after(:create) do |part|
    user = FactoryGirl.create(:user)
    part.widget.user = part.supplier.user = user
  end
end

See also: Get two associations within a Factory to share another association

Upvotes: 8

Related Questions