FactoryGirl и полиморфные ассоциации

Конструкция

У меня есть модель User, которая принадлежит профилю через полиморфную ассоциацию. Причина, по которой я выбрал этот проект, можно найти здесь. Подводя итог, есть много пользователей приложения, которые имеют действительно разные профили.

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

После выбора этого дизайна мне трудно найти хорошие тесты. Используя FactoryGirl и RSpec, я не уверен, как объявить ассоциацию наиболее эффективным способом.

Первая попытка

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

Комментарии/другие мысли

Как упоминалось в комментариях в пользовательской спецификации, использование Artist кажется хрупким. Что делать, если мои заводы меняются в будущем?

Возможно, мне следует использовать заводские обратные вызовы и определить "пользователь-художник" и "музыкант-пользователь"? Весь ввод оценивается.

Ответ 1

Обратные вызовы Factory_Girl облегчили бы жизнь. Как насчет чего-то подобного?

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

Ответ 2

Хотя есть приемлемый ответ, вот какой-то код, использующий новый синтаксис, который работал у меня, и может быть полезен кому-то другому.

спецификации /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

спецификации/модели/artist_spec.rb

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

Будет создан экземпляр исполнителя, а также экземпляр пользователя. Поэтому вы можете позвонить:

@artist.profile

чтобы получить экземпляр Artist.

Ответ 3

Используйте такие черты:

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

теперь вы можете получить пользовательский экземпляр FactoryGirl.create(:user, :artist)

Ответ 4

Вы также можете решить это, используя вложенные фабрики (наследование), таким образом вы создаете базовый factory для каждого класса, тогда которые наследуются от этого основного родителя.

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

Теперь у вас есть доступ к вложенным фабрикам следующим образом:

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

Надеюсь, это поможет кому-то.

Ответ 5

Кажется, что полиморфные ассоциации на фабриках ведут себя так же, как обычные ассоциации Rails.

Итак, есть еще один менее подробный способ, если вам не нравятся атрибуты модели на стороне ассоциации "принадлежность" (Пользователь в этом примере):

# 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: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations

Ответ 6

В настоящее время я использую эту реализацию для работы с полиморфными ассоциациями в FactoryGirl:

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

Затем создайте записи как обычно: FactoryGirl.create(:artist_user)