Factory Girl: How to associate a record to another record without creating a new record?
Asked Answered
R

3

18

I'm using Factory Girl/Rspec2/Rails 3.

In factories.rb, I have:

Factory.define :user do |user|
  user.name       'Some guy'
  user.email      '[email protected]'
  user.password   'password'
end

Factory.define :org_admin, :parent => :user do |user|
  user.email '[email protected]'
end

Factory.define :user_with_membership_request, :parent => :user do |user|
  user.email '[email protected]'
end

Factory.define :organization do |org|
  org.name        'MEC'
  org.description 'Mountain Equipment Co-op'
end

Factory.define :membership do |membership|
  membership.user { Factory(:user) }
  membership.organization { Factory(:organization) }
end

Factory.define :admin_membership, :parent => :membership do |membership|
  membership.user { Factory(:org_admin) }
  membership.is_admin true
  membership.status 'active'
end

Factory.define :membership_request, :parent => :membership do |membership|
  membership.user { Factory(:user_with_membership_request) }
  membership.status 'requested'
end

and then in my rspec test I have:

  it 'should accept the membership request' do
    @org_admin = Factory(:org_admin)
    test_sign_in(@org_admin)
    @organization = Factory(:organization)
    @membership_request = Factory(:membership_request)

    put :update, :organization_id => @organization.id, :id => @membership_request.id, :acceptance => 'approve'
    ...
  end

When I run the test, I get:

 Failure/Error: @membership_request = Factory(:membership_request)
 Validation failed: Name has already been taken

I understand the reason for the failure is because FactoryGirl is creating another organization (with the same name).

But what I'd like to do is create several memberships all associated with the same organization. How do I do that?

Thanks.

Sean

Recoup answered 26/10, 2010 at 19:7 Comment(0)
G
20

You could check for an existing organization and use it, or create one if none exists:

Factory.define :membership do |membership|
  membership.user { Factory(:user) }
  membership.organization { Organization.first || Factory(:organization) }
end

FactoryGirl 4+ update:

Factory.define do
  factory :membership do
    user { create(:user) }
    organization { Organization.first || create(:organization) }
  end
end

Another approach is to use unique identifiers (e.g.: names) for each factory that you want to reuse, then use initialize_with to generate it:

factory :organization_1 do
  ignore { organization_name 'Sample Organization 1' }
  name { organization_name }
  initialize_with { Organization.find_or_create_by_name(organization_name) }
end

Now any reference to :organization_1 will always retrieve the same Organization. Obviously, you must use distinct names for this to work.

Gunfight answered 26/10, 2010 at 19:28 Comment(1)
Thanks Zeteic. I've used this. It works for now. (although when there's more than one Org, I guess I'll need to find another solution.. another problem for another day".Recoup
D
4

There are two things. 1. You might still want to create unique names for Factory(:organisation) you can achieve that using Factory.sequence which will generate it uniquely for you. 2. You can pass in a Factory(:membership_request, :organization => @organization) to use the existing object instead of creating a new one.

Decembrist answered 26/10, 2010 at 19:14 Comment(2)
Thanks Kunday. I've also used this.. it seems like I have to define more than anything basic in rspec test rather than in factories.rb. I still need to understand factories and how to use them at a higher level. I can only mark one of you correct, even though I've used both of your suggestions.Recoup
Cool Sean. From what i noticed of using factory_girl for the past few months is more than 50% of tests depend on some explicit data set up from the rspec test. But given the amount of data setup that you dont do when your model becomes complex its still ok i guess. Nice to hear you got it worked out.Decembrist
T
2

With mongoid you can take combine the use of the #find_or_create_by method with Factory.attributes_for and do something like this

factory :membership do
    organization { Organization.find_or_create_by(Factory.attributes_for(:organization))}
end

I'm sure ActiveRecord has something similar.

Throve answered 11/10, 2011 at 17:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.