简体   繁体   English

具有has_many和has_one的多态关联的Factory Girl

[英]Factory Girl with polymorphic association for has_many and has_one

I am currently working on a project and I wanted to create tests using factory girl but I'm unable to make it work with polymorphic has_many association. 我目前正在开发一个项目,我想用工厂女孩创建测试,但我无法使用多态has_many关联。 I've tried many different possibilities mentioned in other articles but it still doesn't work. 我尝试了其他文章中提到的许多不同的可能性,但它仍然无效。 My model looks like this: 我的模型看起来像这样:

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

So bassically I want to create factory for Restaurant with address and contacts (with validations on Restaurant for presence, but if it's not possible, even without them) but I'm unable to do so. 因此,我想要为餐厅创建一个地址和联系人的工厂(在餐厅进行验证,但如果不可能,即使没有它们),但我无法这样做。 Final syntax should be like: 最终语法应该是:

let(:restaurant) { FactoryGirl.create(:restaurant) }

Which should also create associated address and contacts. 哪个还应该创建相关的地址和联系人。 I have read many articles but I always get some sort of error. 我读了很多文章但总是遇到一些错误。 Currently my factories (sequences are defined correctly) are like this: 目前我的工厂(序列定义正确)是这样的:

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

Is there a way to create restaurant with one command in test with addresses and contacts set? 有没有办法在地址和联系人设置的测试中创建一个带有一个命令的餐馆? Do I really need to get rid off my validations because of after(:create) callback? 因为after(:create)回调,我真的需要摆脱我的验证吗? Current state is as fllowing: 现状如下:

  1. Restaurant is created with name and id. 餐厅创建了名称和ID。
  2. Than the address is being created - all is correcct, it has all the values including addressable_id and addressable_type 正在创建地址 - 所有都是正确的,它具有包括addressable_id和addressable_type在内的所有值
  3. After that all contacts are being creaed, again everything is fine, cntacts has the right values. 在那之后所有的联系人都被创造了,一切都很好,cntacts有正确的价值观。
  4. After that, restaurant doesn't have any ids from associated objects, no association to adddress or contacts 之后,餐厅没有来自关联对象的任何ID,也没有与adddress或联系人的关联
  5. After than, for some reason restaurant is build again (maybe to add those associations?) and it fails: I get ActiveRecord:RecordInvalid. 之后,由于某种原因餐厅再次建立(可能添加这些关联?)并且它失败:我得到ActiveRecord:RecordInvalid。

I'm using factory_girl_rails 4.3.0, ruby 1.9.3 and rails 4.0.1. 我正在使用factory_girl_rails 4.3.0,ruby 1.9.3和rails 4.0.1。 I will be glad for any help. 我会很高兴得到任何帮助。

UPDATE 1: 更新1:

Just for clarification of my goal, I want to be able to create restaurant in my spec using one command and be able to access associated address and contacts, which should be created upon creation of restaurant. 为了澄清我的目标,我希望能够使用一个命令在我的规范中创建餐厅,并能够访问相关的地址和联系人,这应该在创建餐厅时创建。 Let's ignore all validations (I had them commented out in my example from the beginning). 让我们忽略所有的验证(我从一开始就在我的例子中注释了它们)。 When I use after(:build) , first restaurant is created, than address is created with restaurant's ID as addressable_id and class name as addressable_type . 当我使用after(:build)后 ,创建第一个餐馆,而不是使用餐馆ID作为addressable_id和类名作为addressable_type创建地址。 Same goes to contacts, all are correct. 同样适用于联系人,一切都是正确的。 The problem is, that restaurant doesn't know about them (it has no IDs of address or contacts), I can't access them from restaurant which I want to. 问题是,餐厅不知道他们(它没有地址ID或联系人),我无法从我想要的餐厅访问它们。

After really thorough search I have found an answer here - stackoverflow question .This answer also point to this gist . 经过彻底的搜索后,我在这里找到了答案- stackoverflow问题 。这个答案也指向了这个要点 The main thing is to build associations in after(:build) callback and then save them in after(:create) callback. 主要是在after(:build)回调中构建关联,然后将它们保存在after(:create)回调之后。 So it looks like this: 所以它看起来像这样:

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

I had also a bug in my rspec, because I was using before(:each) callback instead of before(:all). 我在我的rspec中也有一个错误,因为我之前使用(:每个)回调而不是之前(:all)。 I hope that this solution helps someone. 我希望这个解决方案有助于某人。

The Problem 问题

Validating the length of a related list of rows is a difficult problem to frame in SQL, so it's a difficult problem to frame in ActiveRecord as well. 验证相关行列表的长度是SQL中框架的难题,因此在ActiveRecord中构建框架也是一个难题。

If you're storing a restaurant foreign key on the addresses table, you can't ever actually create a restaurant that has addresses by the time it's saved, because you need to save the restaurant to get its primary key. 如果您在地址表上存储餐馆外键,您实际上无法创建一个在保存时具有地址的餐馆,因为您需要保存餐馆以获取其主键。 You can get around this problem in ActiveRecord by building up the associated objects in memory, validating against those, and then committing the entire object graph in one SQL transaction. 您可以通过在内存中构建关联对象,验证这些问题,然后在一个SQL事务中提交整个对象图,来解决ActiveRecord中的这个问题。

How to do what you're asking 怎么做你要问的

You can generally get around this by moving things into an after(:build) hook instead of after(:create) . 你通常可以通过将事物移动到after(:build)钩子而不是after(:create) ActiveRecord will save its dependent has_one and has_many associations once it saves itself. 一旦保存自己,ActiveRecord将保存其依赖的has_onehas_many关联。

You're getting errors now because you can't modify an object to satisfy validations in an after(:create) block, because validations have already run by the time the callback runs. 您现在收到错误,因为您无法修改对象以满足after(:create)块中的验证,因为验证已在回调运行时运行。

You can change your restaurant factory to look something like this: 你可以改变你的餐厅工厂看起来像这样:

factory :restaurant do
  name

  after(:build) do |restaurant|
    restaurant.address = build(:restaurant_address, addressable: nil)
    restaurant.contacts = build(:contact, 4, contactable: nil)
  end
end

The nil s there are to break the cyclic relationship between the factories. nil那儿是打破工厂之间的循环关系。 If you do it this way, you can't have a validation on the addressable_id or contactable_id keys, because they won't be available until the restaurant is saved. 如果您这样做,则无法对addressable_idcontactable_id键进行验证,因为在保存restaurant之前它们将无法使用。

Alternatives 备择方案

Although you can get both ActiveRecord and FactoryGirl to do what you're asking, it sets up a precarious list of dependencies which are difficult to understand and are likely to result in leaky validations or unexpected errors like the ones you're seeing now. 虽然您可以同时使用ActiveRecord和FactoryGirl来执行您所要求的操作,但它会设置一个不稳定的依赖项列表,这些依赖项很难理解,并且很可能导致漏洞验证或意外错误,例如您现在看到的错误。

If you're validating contacts this way from the restaurant model because of a form in which you create both a restaurant and its corresponding contacts, you can save yourself a lot of pain by creating a new ActiveModel object to represent that form. 如果您通过这种方式从餐馆模型验证联系人,因为您创建餐馆及其相应联系人的表单,您可以通过创建一个新的ActiveModel对象来表示该表单,从而为您节省很多痛苦。 You can collect the attributes you need for each object there, move some of the validations (especially the ones which validate the length of the contacts list), and then create the object graph on that form in a way that's much clearer and less likely to break. 您可以收集每个对象所需的属性,移动一些验证(尤其是验证联系人列表长度的验证),然后以更清晰,更不可能的方式在该表单上创建对象图。打破。

This has the added benefit of making it easy to create lightweight restaurant objects in other tests which don't need to worry about contacts or addresses. 这样做的另一个好处是可以轻松地在其他测试中创建轻量级restaurant对象,而无需担心联系人或地址。 If you force your factories to create these dependent objects every time, you'll quickly run into two problems: 如果强制工厂每次都创建这些依赖对象,那么很快就会遇到两个问题:

  • Your tests will be painfully slow. 你的测试会非常缓慢。 Creating five dependent records every time you want to work with a restaurant won't scale very far. 每次想要与餐厅合作时创建五个相关记录将不会扩展到很远。
  • If you ever want to specify different contacts or addresses in your tests, you'll constantly be fighting with your factories. 如果您想在测试中指定不同的联系人或地址,您将不断与您的工厂抗争。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM