Factory Girl with polymorphic association for has_many and has_one

本小妞迷上赌 提交于 2019-12-03 20:32:23
Giron

After really thorough search I have found an answer here - stackoverflow question.This answer also point to this gist. The main thing is to build associations in after(:build) callback and then save them in after(:create) callback. 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). 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.

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.

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). ActiveRecord will save its dependent has_one and has_many associations once it saves itself.

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.

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 nils there are to break the cyclic relationship between the factories. 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.

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.

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. 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. 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.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!