FactoryGirl and polymorphic associations

Ruby on-RailsFactory Bot

Ruby on-Rails Problem Overview


The design

I have a User model that belongs to a profile through a polymorphic association. The reason I chose this design can be found here. To summarize, there are many users of the application that have really different profiles.

class User < ActiveRecord::Base
  belongs_to :profile, :dependent => :destroy, :polymorphic => true
end

class Artist < ActiveRecord::Base
  has_one :user, :as => :profile
end

class Musician < ActiveRecord::Base
  has_one :user, :as => :profile
end

After choosing this design, I'm having a hard time coming up with good tests. Using FactoryGirl and RSpec, I'm not sure how to declare the association the most efficient way.

First attempt

factories.rb

Factory.define :user do |f|
  # ... attributes on the user
  # this creates a dependency on the artist factory
  f.association :profile, :factory => :artist 
end

Factory.define :artist do |a|
  # ... attributes for the artist profile
end

user_spec.rb

it "should destroy a users profile when the user is destroyed" do
  # using the class Artist seems wrong to me, what if I change my factories?
  user = Factory(:user)
  profile = user.profile
  lambda { 
    user.destroy
  }.should change(Artist, :count).by(-1)
end
Comments / other thoughts

As mentioned in the comments in the user spec, using Artist seems brittle. What if my factories change in the future?

Maybe I should use factory_girl callbacks and define an "artist user" and "musician user"? All input is appreciated.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Although there is an accepted answer, here is some code using the new syntax which worked for me and might be useful to someone else.

spec/factories.rb

FactoryGirl.define do
   
  factory :musical_user, class: "User" do
    association :profile, factory: :musician
    #attributes for user
  end

  factory :artist_user, class: "User" do
    association :profile, factory: :artist
    #attributes for user
  end

  factory :artist do
    #attributes for artist
  end

  factory :musician do
    #attributes for musician
  end
end

spec/models/artist_spec.rb

before(:each) do
  @artist = FactoryGirl.create(:artist_user)
end

Which will create the artist instance as well as the user instance. So you can call:

@artist.profile

to get the Artist instance.

Solution 2 - Ruby on-Rails

Use traits like this;

FactoryGirl.define do
    factory :user do
        # attributes_for user
        trait :artist do
            association :profile, factory: :artist
        end
        trait :musician do
            association :profile, factory: :musician
        end
    end
end

now you can get user instance by FactoryGirl.create(:user, :artist)

Solution 3 - Ruby on-Rails

Factory_Girl callbacks would make life much easier. How about something like this?

Factory.define :user do |user|
  #attributes for user
end

Factory.define :artist do |artist|
  #attributes for artist
  artist.after_create {|a| Factory(:user, :profile => a)}
end

Factory.define :musician do |musician|
  #attributes for musician
  musician.after_create {|m| Factory(:user, :profile => m)}
end

Solution 4 - Ruby on-Rails

You can also solve this using nested factories (inheritance), this way you create a basic factory for each class then nest factories that inherit from this basic parent.

FactoryGirl.define do
	factory :user do
		# attributes_for user
		factory :artist_profile do
			association :profile, factory: :artist
		end
		factory :musician_profile do
			association :profile, factory: :musician
		end
	end
end

You now have access to the nested factories as follows:

artist_profile = create(:artist_profile)
musician_profile = create(:musician_profile)

Hope this helps someone.

Solution 5 - Ruby on-Rails

It seems that polymorphic associations in factories behave the same as regular Rails associations.

So there is another less verbose way if you don't care about attributes of model on "belongs_to" association side (User in this example):

# Factories
FactoryGirl.define do
  sequence(:email) { Faker::Internet.email }

  factory :user do
    # you can predefine some user attributes with sequence
    email { generate :email }
  end

  factory :artist do
    # define association according to documentation
    user 
  end
end

# Using in specs    
describe Artist do      
  it 'created from factory' do
    # its more naturally to starts from "main" Artist model
    artist = FactoryGirl.create :artist        
    artist.user.should be_an(User)
  end
end

FactoryGirl associations: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations

Solution 6 - Ruby on-Rails

I currently use this implementation for dealing with polymorphic associations in FactoryGirl:

In /spec/factories/users.rb:

FactoryGirl.define do

  factory :user do
    # attributes for user
  end

  # define your Artist factory elsewhere
  factory :artist_user, parent: :user do
    profile { create(:artist) }
    profile_type 'Artist'
    # optionally add attributes specific to Artists
  end

  # define your Musician factory elsewhere
  factory :musician_user, parent: :user do
    profile { create(:musician) }
    profile_type 'Musician'
    # optionally add attributes specific to Musicians
  end

end

Then, create the records as usual: FactoryGirl.create(:artist_user)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFeechView Question on Stackoverflow
Solution 1 - Ruby on-Railsveritas1View Answer on Stackoverflow
Solution 2 - Ruby on-RailskuboonView Answer on Stackoverflow
Solution 3 - Ruby on-RailsmembLoperView Answer on Stackoverflow
Solution 4 - Ruby on-RailsKingsley IjomahView Answer on Stackoverflow
Solution 5 - Ruby on-RailsDarksideView Answer on Stackoverflow
Solution 6 - Ruby on-RailsvichView Answer on Stackoverflow