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)