I was pairing with Zi at work today, and we hit onto an interesting problem.
The Problem
We were working on a Spree application, which is a really nice Rails engine/gem e-commerce platform. Out of the box, it contains a bunch of factories. Here’s the default user_factory.rb
in Spree:
FactoryGirl.define do
sequence :user_authentication_token do |n|
"xxxx#{Time.now.to_i}#{rand(1000)}#{n}xxxxxxxxxxxxx"
end
factory :user, class: Spree.user_class do
email { generate(:random_email) }
login { email }
password 'secret'
password_confirmation { password }
authentication_token { generate(:user_authentication_token) } if Spree.user_class.attribute_method? :authentication_token
factory :admin_user do
spree_roles { [Spree::Role.find_by(name: 'admin') || create(:role, name: 'admin')] }
end
factory :user_with_addreses do
ship_address
bill_address
end
end
end
Now, our Rails application which uses Spree has an extra attribute username
. We could have use traits, and create a new factory like :user_with_username
. The tiny problem with that is the original :user
factory is used EVERYWHERE.
One terrifying solution would have been to crack open the Spree gem and make the modification there. My suggestion was quickly dismissed.
We tried something like this:
FactoryGirl.define do
sequence :user_name do |n|
"user_name_{rand(1000)}#{n}"
end
factory :user, class: Spree.user_class do
user_name { generate(:user_name) }
end
end
Unfortunately, this only throws a DuplicateDefinitionError: :user already registered: user
exception.
We tried unregistering a factory, especially since there’s a Factory.register
, but again no luck. We tried deleting a factory, and we were left depressed.
The Solution
The solution wasn’t complicated. Finding it on Google and StackOverflow was. This explains the post’s title.
There are essentially 2 steps to this solution.
Step 1: Make sure Spree’s factories are loaded first.
In spec_helper.rb
, you need to add require 'spree/testing_support/factories
in order to use Spree’s built-in factories. Make sure this is included before the your custom factories.
That’s because you are going to modify Spree’s factories, so you need to make sure that the Spree one gets loaded first.
Step 2: Modify the Factory
I never knew Factory.modify
existed:
Factory.define do
sequence :user_name do |n|
"user_name_{rand(1000)}#{n}"
end
end
Factory.modify do
factory :user do
user_name { generate(:user_name) }
end
end
Note that the sequence :user_name
has to go into it’s own Factory.define
block. Therefore, this doesn’t work:
Factory.modify do
sequence :user_name do |n|
"user_name_{rand(1000)}#{n}"
end
factory :user do
user_name { generate(:user_name) }
end
end
That’s it!
We saw green again, and prevented anyone of us from getting bald. Hope this saves someone a couple of hours work.
Thanks for reading!