Factory Girl create association with existing object
Asked Answered
A

3

11

I am new to FactoryGirl and I am trying the following simple scenario?

factory :female, :class => Gender do
  code 'Female'
end

factory :male, :class => Gender do
  code 'Male'
end

factory :papas, :class => Customer do
  first_name 'Jim'
  last_name 'Papas'
  association :gender, :factory => :male, :strategy => :build
end

factory :dumas, :class => Customer do
  first_name 'Mary'
  last_name 'Dumas'
  association :gender, :factory => :female, :strategy => :build
end

Then in my test:

 create(:male)
 create(:female)
 create(:papas)
 create(:dumas)

Note that Customer class has an assocation belongs_to Gender class and a validation rule that says that gender_id should be present. I also have a validation on Gender class for uniqueness on code.

On create(:papas) statement above, in my test, I get the error that the Customer that is going to be created is not valid, because gender_id is nil.

If I remove the :strategy => :build on Customer :papas factory association with gender, then I will get an error, that when trying to create :papas, the code for the gender already exists.

What do I need to do so my tests create the data as required above?

Note that I want to have genders created without customers also, in other tests. Do not tell me to create the customers with factory create commands and let the customers create the genders at the same time. This will not work if I try to create two customers of the same gender too.

Also, there has to be a better answer than the one:

@male = create(:male)
@female = create(:female)
create(:papas, :gender => @male)
create(:dumas, :gender => @female)

(When using fixtures these things were ready out-of-the-box. Shall I return back to fixtures?)

Allonge answered 1/6, 2012 at 8:15 Comment(0)
I
14

You could achieve what you want by both not using a factory for your Gender instances and using callbacks in your Customer factory.

You're not gaining a lot from the Gender factory as it stands (although I appreciate it may be simplified for the purposes of asking this question). You could just as easily do this to get a Gender:

Gender.find_or_create_by_code!('Male')

Then in your Customer factory you can use a before callback to set the Gender:

factory :dumas, :class => Customer do
  first_name 'Mary'
  last_name 'Dumas'
  before_create do |customer|
    customer.gender = Gender.find_or_create_by_code!('Female')
  end
end

The format for using callbacks in factory_girl has recently changed so if you're using 3.3 or later you'll need to do:

before(:create) do |customer|

instead of before_create.

If creating a gender is more complex than your example you can still create the genders using a factory but hook them up with your customers using callbacks using find_by_code! insteand of find_or_create_by_code!.

Inlier answered 1/6, 2012 at 8:44 Comment(2)
Though quite verbose vs fixtures, your answer is correct. I have been using factory girl the last two months and still my next project will be with fixtures!Allonge
The advantage you might find with factories is if you need to create a lot of different customers. You can have a male_customer and female_customer factory and use those as the parent factories for your different customers - that reduces the verbosity considerably. Glad it worked though!Inlier
K
6

ignore has been deprecated and will be removed on version 5 of Factory Girl.

Instead, you can use transient attributes:

factory :dumas, :class => Customer do
  transient do
    female { Gender.find_by_code('Female') || create(:female) }
  end

  first_name 'Mary'
  last_name 'Dumas'
  gender { female }
end
Kare answered 20/10, 2015 at 8:16 Comment(0)
R
5

The ignore block can be used to wrap this up as well:

factory :dumas, :class => Customer do
  ignore do
    female { Gender.find_by_code('Female') || create(:female) }
  end

  first_name 'Mary'
  last_name 'Dumas'
  gender { female }
end
Robby answered 25/6, 2013 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.