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.