If you have an ActiveRecord model associated with another model that has a uniqueness constraint, modeling it in FactoryGirl when writing specs doesn’t work out of the box. But there is a simple solution.
Let’s say you have a UserType ActiveRecord model which has a value field, with a unique constraint. Which means that in your DB you don’t have two records in user_types table with the same value field. You would have entries with value being admin, guest, student, teacher, etc. Now in your specs you want to declare a factory for each UserType. So using FactoryGirl, you are going to write something like this:
FactoryGirl.define do
factory :user_type do
value "some_user_type"
factory :admin_user_type do
value "admin"
adds_locations true
adds_organizations true
manage_teachers true
end
factory :guest_user_type do
value "guest"
adds_locations false
adds_organizations false
manage_teachers false
end
#... and so forth
end
end
So far so good. You also decide to define factories for each kind of user:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user-#{n}@myapp.com" }
password "Password123"
association :user_type, factory: :user_type
factory :guest do
association :user_type, factory: :guest_user_type
end
end
end
Now somewhere in your spec you create two different guest users:
guest1 = create(:guest) guest2 = create(:guest)
Of course you expect each guest object to be associated with the same UserType object. Let’s check:
>> User.all.map{|u| [u.email, u.user_type_id]}
# prints:[user-1@myapp.com, 1], [user-2@myapp.com, 2]
And here is the surprise: each guest is associated with a different UserType object, one with id=1 and another with id=2. This is certainly not what we expected, so what’s going on?
Turns out that every time you call create(:my_factory), FactoryGirl creates a new object for every association that my_factory has. And that’s a problem if you expect every guest to be associated with the same UserType object. So what do we do?
The solution is simple: FactoryGirl allows you to initialize your factories in way that would respect uniqueness. Let’s revisit our UserType factories:
FactoryGirl.define do
factory :user_type do
value "some_user_type"
factory :admin_user_type do
value "admin"
adds_locations true
adds_organizations true
manage_teachers true
initialize_with { UserType.find_or_create_by(value: 'admin') }
end
factory :guest_user_type do
value "guest"
adds_locations false
adds_organizations false
manage_teachers false
initialize_with { UserType.find_or_create_by(value: 'guest') }
end
end
end
Using initialize_with we initialize each new guest user object with the same user_type we created previously. Let’s try to create two guests again and see if anything changed:
>> User.all.map{|u| [u.email, u.user_type_id]}
# prints: [user-1@myapp.com, 1], [user-2@myapp.com, 1]
Great, we see that now each user is indeed associated with the same guest user_type record which has id=1.
And now, feel free to go and make yourself a cup of coffee, you earned it.