我目前正在开发一个项目,我想使用工厂女孩创建测试,但我无法使用多态has_many关联。我尝试了其他文章中提到的许多不同的可能性,但它仍然无效。我的模型看起来像这样:
class Restaurant < ActiveRecord::Base
has_one :address, as: :addressable, dependent: :destroy
has_many :contacts, as: :contactable, dependent: :destroy
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :address, allow_destroy: true
validates :name, presence: true
#validates :address, presence: true
#validates :contacts, presence: true
end
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
# other unimportant validations, address is created valid, the problem is not here
end
class Contact < ActiveRecord::Base
belongs_to :contactable, polymorphic: true
# validations ommitted, contacts are created valid
end
所以贝司我想为餐厅创建一个地址和联系人的工厂(在餐厅有验证,但如果不可能,即使没有它们),但我无法这样做。最终语法应该是:
let(:restaurant) { FactoryGirl.create(:restaurant) }
还应该创建相关的地址和联系人。我读了很多文章但总是遇到一些错误。目前我的工厂(序列定义正确)是这样的:
factory :restaurant do
name
# address {FactoryGirl.create(:address, addressable: aaa)}
# contacts {FactoryGirl.create_list(:contact,4, contactable: aaa)}
# Validations are off, so this callback is possible
after(:create) do |rest|
# Rest has ID here
rest.address = create(:restaurant_address, addressable: rest)
rest.contacts = create_list(:contact,4, contactable: rest)
end
end
factory :restaurant_address, class: Address do
# other attributes filled from sequences...
association :addressable, factory: :restaurant
# addressable factory: restaurant
# association(:restaurant)
end
factory :contact do
contact_type
value
association :contactable, :factory => :restaurant
end
有没有办法在测试中使用一个命令创建带有地址和联系人设置的餐馆?因为after(:create)回调,我真的需要摆脱我的验证吗? 现状如下:
我正在使用factory_girl_rails 4.3.0,ruby 1.9.3和rails 4.0.1。我会很高兴得到任何帮助。
更新1:
为了澄清我的目标,我希望能够使用一个命令在我的规范中创建餐厅,并能够访问相关的地址和联系人,这应该在创建餐厅时创建。让我们忽略所有的验证(我从一开始就在我的例子中注释了它们)。当我使用之后(:build)时,会创建第一个餐厅,而不是使用餐馆ID作为 addressable_id 创建地址,将类名称创建为 addressable_type 。同样适用于联系人,一切都是正确的。问题是,餐厅不知道他们(它没有地址ID或联系人),我无法从我想要的餐厅访问它们。
答案 0 :(得分:7)
经过彻底彻底的搜索,我找到了答案here - stackoverflow question。这个答案也指向了gist。主要是在after(:build)回调中构建关联,然后将它们保存在after(:create)回调之后。所以它看起来像这样:
factory :restaurant do
name
trait :confirmed do
state 1
end
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: restaurant)
restaurant.contacts = build_list(:contact,4, contactable: restaurant)
end
after(:create) do |restaurant|
restaurant.contacts.each { |contact| contact.save! }
restaurant.address.save!
end
end
我的rspec中也有一个错误,因为我之前使用的是:(每个)回调而不是之前(:all)。我希望这个解决方案有助于某人。
答案 1 :(得分:1)
问题
验证相关行列表的长度是SQL中框架的难题,因此在ActiveRecord中构建框架也是一个难题。
如果您在地址表中存储餐馆外键,您实际上无法在保存时创建具有地址的餐馆,因为您需要保存餐馆以获取其主键。您可以通过在内存中构建关联对象,验证这些问题,然后在一个SQL事务中提交整个对象图,来解决ActiveRecord中的这个问题。
如何做你要问的事
您通常可以通过将内容移动到after(:build)
挂钩而不是after(:create)
来解决此问题。一旦保存自己,ActiveRecord将保存其依赖的has_one
和has_many
关联。
您现在收到错误,因为您无法修改对象以满足after(:create)
块中的验证,因为验证已在回调运行时运行。
您可以将餐馆工厂更改为以下内容:
factory :restaurant do
name
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: nil)
restaurant.contacts = build(:contact, 4, contactable: nil)
end
end
nil
可以打破工厂之间的循环关系。如果您这样做,则无法对addressable_id
或contactable_id
键进行验证,因为在restaurant
保存之前它们将无法使用。
<强>替代强>
虽然你可以让ActiveRecord和FactoryGirl同时做你所要求的事情,但它会设置一个不稳定的依赖列表,这些依赖列表难以理解,并且很可能导致漏洞验证或意外错误,例如你所看到的错误现在
如果您通过这种方式从餐馆模型验证联系人,因为您创建了一个餐馆及其相应的联系人,您可以通过创建一个新的ActiveModel
对象来节省很多痛苦代表那种形式。您可以收集每个对象所需的属性,移动一些验证(尤其是验证联系人列表长度的验证),然后以更清晰,更不可能的方式在该表单上创建对象图。断裂。
这样做的另一个好处是可以轻松地在其他测试中创建轻量级restaurant
对象,而无需担心联系人或地址。如果强制工厂每次都创建这些依赖对象,那么很快就会遇到两个问题: