Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory Girl create association with existing object

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?)

like image 959
p.matsinopoulos Avatar asked Jun 01 '12 08:06

p.matsinopoulos


3 Answers

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!.

like image 136
Shadwell Avatar answered Nov 15 '22 09:11

Shadwell


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
like image 21
Kostas Rousis Avatar answered Nov 15 '22 10:11

Kostas Rousis


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
like image 38
Dave Newton Avatar answered Nov 15 '22 08:11

Dave Newton