web-dev-qa-db-fra.com

FactoryGirl et associations polymorphes

La conception

J'ai un modèle utilisateur qui appartient à un profil via une association polymorphe. La raison pour laquelle j'ai choisi cette conception peut être trouvée ici . Pour résumer, de nombreux utilisateurs de l'application ont des profils vraiment différents.

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

Après avoir choisi ce design, j'ai du mal à trouver de bons tests. En utilisant FactoryGirl et RSpec, je ne sais pas comment déclarer l'association de la manière la plus efficace.

Premier essai

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

Commentaires/autres réflexions

Comme mentionné dans les commentaires dans les spécifications utilisateur, l'utilisation d'Artist semble fragile. Et si mes usines changent à l'avenir?

Peut-être devrais-je utiliser callbacks factory_girl et définir un "utilisateur artiste" et "utilisateur musicien"? Toutes les contributions sont appréciées.

66
Feech

Les rappels Factory_Girl rendraient la vie beaucoup plus facile. Que diriez-vous quelque chose comme ça?

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
11
membLoper

Bien qu'il existe une réponse acceptée, voici du code utilisant la nouvelle syntaxe qui a fonctionné pour moi et pourrait être utile à quelqu'un d'autre.

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

Ce qui créera l'instance d'artiste ainsi que l'instance d'utilisateur. Vous pouvez donc appeler:

@artist.profile

pour obtenir l'instance Artist.

145
veritas1

Utilisez des traits comme celui-ci;

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

vous pouvez maintenant obtenir l'instance utilisateur par FactoryGirl.create(:user, :artist)

36
kuboon

Vous pouvez également résoudre ce problème en utilisant des usines imbriquées (héritage), de cette façon, vous créez une usine de base pour chaque classe, puis imbriquez des usines qui héritent de ce parent de base.

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

Vous avez maintenant accès aux usines imbriquées comme suit:

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

J'espère que cela aide quelqu'un.

5
Kingsley Ijomah

Il semble que les associations polymorphes dans les usines se comportent de la même manière que les associations Rails.

Il y a donc une autre manière moins verbeuse si vous ne vous souciez pas des attributs du modèle du côté de l'association "appartient à" (l'utilisateur dans cet exemple):

# 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

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

2
Darkside

J'utilise actuellement cette implémentation pour gérer les associations polymorphes dans FactoryGirl:

Dans /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

Ensuite, créez les enregistrements comme d'habitude: FactoryGirl.create(:artist_user)

1
vich