FactoryGirl (1-1 Associations)

This blog post goes about a common problem I’ve haven’t found much documentation on.

It follows issues mentioned of multiple places like here and variations of it.

I’ve written some code to illustrate the problem on my Github FactoryGirl-Problem to accompany this.

Problem

How to create factories such that the association is a 1-1, and all models can be created independently.

Models

# app/models/user.rb
class User < ActiveRecord::Base
  has_one :profile
end

# app/models/profile.rb
class Profile < ActiveRecord::Base
  belongs_to :user

  validates :user,
    presence: true,
    uniqueness: true
end

How to create the factories to get the following tests to pass

# spec/models/user_spec.rb
describe User do
  describe 'creating' do
    subject { create(:user) }

    it 'can be done' do
      expect(subject).not_to be_nil
    end

    it 'has the profile association' do
      expect(subject.profile).not_to be_nil
    end
  end
end

# spec/models/profile_spec.rb
describe Profile do
  describe 'creating' do
    subject { create(:profile) }

    it 'can be done' do
      expect(subject).not_to be_nil
    end

    it 'has the user association' do
      expect(subject.user).not_to be_nil
    end
  end
end

The circular dependency of associations make it hard to create.

Bad Solution

# spec/factories/profiles.rb
FactoryGirl.define do
  factory :profile do
    user
  end
end

# spec/factories/users.rb
FactoryGirl.define do
  factory :user do
    name 'MyString'
    profile
  end
end

This fails due to a circular dependency: user creates a profile, and profile creates a user.

Many tries were made including after(:build), after(:create) hooks into factories amongst others.

After much attempt to find solution online, I managed to find the following to solve my issue

Solution

The solution I came up with after a lot of time is the following:

# spec/factories/profiles.rb
FactoryGirl.define do
  factory :profile do
    association :user, ignore_profile: true
  end
end

# spec/factories/users.rb
FactoryGirl.define do
  factory :user do
    name 'MyString'

    ignore do
      ignore_profile false
    end

    after(:create) do |user, evaluator|
      user.profile ||= build(:profile, user: user) unless evaluator.ignore_profile
    end
  end
end

This takes advantage of the trait to build a profile only after the user is created. For most cases, this should do the job.

Moving forward

I’d like to be able to change the user model to the following:

# app/models/user.rb
class User < ActiveRecord::Base
  has_one :profile
  validates :profile, presence: true
end

However, this fails tests as user cannot be crated.

I’d also like to create stubbed objects by adding the following to the tests suite

# app/spec/user_spec.rb
describe User do
  describe 'stubbing' do
    subject { build_stubbed(:user) }

    it 'can be done' do
      expect(subject).not_to be_nil
    end

    it 'has the profile association' do
      expect(subject.profile).not_to be_nil
    end
  end
end

# app/spec/profile_spec.rb
describe Profile
  describe 'stubbing' do
    subject { build_stubbed(:profile) }

    it 'can be done' do
      expect(subject).not_to be_nil
    end

    it 'has the user association' do
      expect(subject.user).not_to be_nil
    end
  end
end

When I find a solution somewhere, I will come and update this blogpost.

Conclusion

{% raw %}

In the meantime, I have a solution that does the job for most of the cases.


Rails