FactoryGirl和多态关联


69

该设计

我有一个通过多态关联属于某个配置文件的用户模型。我选择此设计的原因可以在这里找到。总而言之,该应用程序有许多用户具有完全不同的配置文件。

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,我不确定如何以最有效的方式声明关联。

第一次尝试

factory.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似乎很脆弱。如果我的工厂将来发生变化该怎么办?

也许我应该使用factory_girl回调并定义“艺术家用户”和“音乐家用户”?感谢所有输入。


喜欢这个问题,谢谢!
米卡2014年

Answers:


13

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

1
是的,这看起来不错。我觉得我可以为此目的使用回调。关于此解决方案的两个问题。1)在测试用户模型的:dependent => destroy选项时,我是否应该只选择一个随机配置文件工厂(即,音乐家或艺术家)使用?2)您是否建议针对每种配置文件模型测试相同的功能?即,对于音乐家和艺术家,我是否都应该进行“应检索关联的用户对象”测试?
Feech

1)您可以尝试类似'user = Factory(:user)artist = Factory(:artist,:user => user)user.destroy!artist.reload!.valid?.should be_false'2)IMO,应该花更少的精力在测试应用程序逻辑上。我会限制自己对Aritst'has_one'用户的测试,对用户也是如此。
membLoper 2011年

147

尽管有一个公认的答案,但是这里有一些使用新语法的代码对我有用,对其他人可能有用。

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

规格/型号/artist_spec.rb

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

这将创建艺术家实例以及用户实例。因此,您可以致电:

@artist.profile

获取Artist实例。


谢谢!为了进一步说明,“#attributes for user”不必是一个块(我很困惑)。它可以简单地是用户名'user1'电子邮件'user@test.com'等,作为musicic_user / artist_user工厂的常规属性列出。
米卡2014年

1
不要使用此答案。您必须为:musician_user和:artist_user复制相同的属性。不干。使用kuboon或Kingsley Ijomah的答案。就是说,我相信这个答案是帮助我们找到正确答案的有用的垫脚石。
Arcolye 2015年

@Arcolye,不是干的,我假设您指的是还有更多的工厂需要创建。但是,我看不到您建议的其他两个答案如何减少工厂/特性的数量。难道你不会遇到同样的事情吗?
Leo Lei

38

使用这样的特征;

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)


5

您还可以使用嵌套工厂(继承)解决此问题,这样您可以为每个类创建一个基本工厂,然后嵌套从该基本父级继承的工厂。

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)

希望这对某人有帮助。


2
嵌套的工厂可能变得很困难。这里的性格是可取的。如果您需要工厂作为子类,请创建一个定义父项的子工厂。

2

看来工厂中的多态关联的行为与常规的Rails关联相同。

因此,如果您不在乎“ belongs_to”关联侧的模型属性(在此示例中为User),则还有另一种较为冗长的方法:

# 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


1

我目前正在使用此实现来处理中的多态关联 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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.