ruby on rails - rails - FactoryGirl y asociaciones polimórficas
rspec factory bot (6)
El diseño
Tengo un modelo de usuario que pertenece a un perfil a través de una asociación polimórfica. La razón por la que elegí este diseño se puede encontrar here . En resumen, hay muchos usuarios de la aplicación que tienen perfiles realmente diferentes.
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
Después de elegir este diseño, estoy teniendo dificultades para realizar buenas pruebas. Usando FactoryGirl y RSpec, no estoy seguro de cómo declarar la asociación de la manera más eficiente.
Primer intento
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
Comentarios / otros pensamientos
Como se menciona en los comentarios en la especificación del usuario, usar Artist parece frágil. ¿Qué pasa si mis fábricas cambian en el futuro?
¿Tal vez debería usar las devoluciones de llamada factory_girl y definir un "usuario de artista" y "usuario de músico"? Toda la entrada es apreciada.
Actualmente uso esta implementación para manejar asociaciones polimórficas en FactoryGirl
:
En /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
Luego, cree los registros como de costumbre: FactoryGirl.create(:artist_user)
Aunque hay una respuesta aceptada, aquí hay un código que usa la nueva sintaxis que funcionó para mí y podría ser útil para otra persona.
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
Que creará la instancia del artista así como la instancia del usuario. Entonces puedes llamar:
@artist.profile
para obtener la instancia de artista.
Las devoluciones de llamada Factory_Girl harían la vida mucho más fácil. ¿Qué tal algo como esto?
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
Parece que las asociaciones polimórficas en las fábricas se comportan de la misma manera que las asociaciones regulares de Rails.
Entonces, hay otra manera menos detallada si no te importan los atributos del modelo en el lado de la asociación "belongs_to" (usuario en este ejemplo):
# 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
Asociaciones de FactoryGirl: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
También puede resolver esto usando fábricas anidadas (herencia), de esta manera puede crear una fábrica básica para cada clase y luego anidar las fábricas que heredan de este principal básico.
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
Ahora tiene acceso a las fábricas anidadas de la siguiente manera:
artist_profile = create(:artist_profile)
musician_profile = create(:musician_profile)
Espero que esto ayude a alguien.
Usa rasgos como este;
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
ahora puede obtener la instancia de usuario de FactoryGirl.create(:user, :artist)