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!