【问题标题】:Factory Girl with polymorphic association for has_many and has_one具有 has_many 和 has_one 多态关联的 Factory Girl
【发布时间】:2014-01-21 22:05:46
【问题描述】:

我目前正在做一个项目,我想使用 factory girl 创建测试,但我无法使其与多态 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) 回调,我真的需要摆脱我的验证吗? 当前状态如下:

  1. 餐厅是使用名称和 ID 创建的。
  2. 地址正在被创建 - 一切都是正确的,它具有所有值,包括 addressable_id 和 addressable_type
  3. 在创建所有联系人之后,一切正常,cntacts 具有正确的值。
  4. 此后,餐厅没有来自关联对象的任何 ID,也没有与地址或联系人的关联
  5. 之后,由于某种原因,重新构建了餐厅(可能是添加这些关联?)但它失败了:我得到 ActiveRecord:RecordInvalid。

我正在使用 factory_girl_rails 4.3.0、ruby 1.9.3 和 rails 4.0.1。我会很高兴得到任何帮助。

更新 1:

只是为了澄清我的目标,我希望能够使用一个命令在我的规范中创建餐厅,并能够访问应在创建餐厅时创建的关联地址和联系人。让我们忽略所有验证(我从一开始就在我的示例中将它们注释掉了)。当我使用 after(:build) 时,会创建第一家餐厅,然后使用餐厅 ID 为 addressable_id 和类名称为 addressable_type 创建地址。联系人也是如此,一切都是正确的。问题是,那家餐厅不知道它们(它没有地址或联系人的 ID),我无法从我想要的餐厅访问它们。

【问题讨论】:

    标签: ruby-on-rails rspec factory-bot


    【解决方案1】:

    经过彻底的搜索,我找到了答案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 中也有一个错误,因为我使用的是 before(:each) 回调而不是 before(:all)。我希望这个解决方案对某人有所帮助。

    【讨论】:

      【解决方案2】:

      问题

      验证相关行列表的长度是SQL中的一个难题,因此在ActiveRecord中也是一个难题。

      如果您将餐厅外键存储在地址表中,则您永远无法真正创建在保存地址时具有地址的餐厅,因为您需要保存餐厅以获取其主键。您可以在 ActiveRecord 中通过在内存中构建关联对象、验证这些对象、然后在一个 SQL 事务中提交整个对象图来解决此问题。

      如何按照您的要求进行操作

      您通常可以通过将内容移至 after(:build) 挂钩而不是 after(:create) 来解决此问题。 ActiveRecord 将在保存自己后保存其依赖的 has_onehas_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
      

      nils 是为了打破工厂之间的循环关系。如果这样做,则无法对 addressable_idcontactable_id 键进行验证,因为在保存 restaurant 之前它们将不可用。

      替代品

      虽然您可以同时让 ActiveRecord 和 FactoryGirl 执行您的要求,但它会设置一个不稳定的依赖项列表,这些依赖项难以理解,并且可能会导致泄漏验证或您所看到的意外错误现在。

      如果您从餐厅模型中以这种方式验证联系人,因为您在其中创建餐厅及其相应联系人的表单,您可以通过创建一个新的 ActiveModel 对象来表示它,从而为自己省去很多麻烦形式。您可以在那里收集每个对象所需的属性,移动一些验证(尤其是验证联系人列表长度的验证),然后以更清晰且不太可能的方式在该表单上创建对象图休息。

      这还有一个额外的好处,即可以轻松地在其他不需要担心联系人或地址的测试中创建轻量级 restaurant 对象。如果你强制你的工厂每次都创建这些依赖对象,你很快就会遇到两个问题:

      • 您的测试会非常缓慢。每次您想与餐厅合作时创建五个相关记录不会扩展太多。
      • 如果您想在测试中指定不同的联系人或地址,您将经常与您的工厂争吵。

      【讨论】:

      • 感谢您的宝贵时间,我已经更新了我的问题,请忽略所有验证,我的目标只是创建带有地址和联系人的餐厅,这些地址和联系人应该可以从餐厅的规范中访问。尝试在 addressable 中设置 nil 没有帮助,地址和联系人只是在没有餐厅 ID 的情况下创建,因此关联被破坏得更多。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多