68 votes

FactoryGirl et les associations polymorphes

La conception

J'ai un modèle d'utilisateur qui appartient à un profil par le biais d'une association polymorphe. La raison pour laquelle j'ai choisi cette conception peut être trouvée aquí . En résumé, il y a de nombreux utilisateurs de l'application qui ont des profils très 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 modèle, 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.

Première tentative

usines.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 de la spécification de l'utilisateur, l'utilisation d'Artist semble fragile. Que se passera-t-il si mes usines changent à l'avenir ?

Je devrais peut-être utiliser callbacks factory_girl et définir un "utilisateur artiste" et un "utilisateur musicien" ? Tous les commentaires sont les bienvenus.

147voto

veritas1 Points 2036

Bien qu'il y ait une réponse acceptée, voici un code utilisant la nouvelle syntaxe qui a fonctionné pour moi et qui 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 de l'artiste ainsi que l'instance de l'utilisateur. Vous pouvez donc appeler :

@artist.profile

pour obtenir l'instance de l'artiste.

37voto

kuboon Points 1033

Utilisez les caractéristiques suivantes ;

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

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

13voto

membLoper Points 442

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

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

5voto

Kingsley Ijomah Points 193

Vous pouvez également résoudre ce problème en utilisant des fabriques imbriquées (héritage), de cette façon vous créez une fabrique de base pour chaque classe puis puis vous imbriquez les 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 aidera quelqu'un.

2voto

Darkside Points 173

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

Il existe donc une autre méthode moins verbeuse si vous ne vous souciez pas des attributs du modèle du côté de l'association "belongs_to" (User 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X