FactoryGirl complex association with multiple models
Asked Answered
O

2

6

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

Otto answered 24/10, 2012 at 1:55 Comment(0)
U
8

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

Uncivil answered 24/10, 2012 at 2:30 Comment(4)
Thanks a lot. I ended up using after(:build) and part.widget.user = part.supplier.user that way I'm not creating 3 users :)Otto
Oh great that it worked! I wasn't quite sure it would. It still seems to me there must be a cleaner way to do it though.Uncivil
That's what I thought too, but association :supplier, user: widget.user doesn't work might be something to look into for a patchOtto
Yeah I was thinking something like creating a user in a before block, then assigning it to both supplier and widget in the main block. But I don't think you can do that.Uncivil
S
0

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
Saintmihiel answered 7/2, 2016 at 23:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.