Consistently Persist PD FactoryBot Records in v5 - #51341
Conversation
In preparation for an update to FactoryBot 5, which fixes some unintuitive behavior when `build`ing objects with descendents. Specifically, the `:pd_teacher_application` factory calls `build :pd_teacher_application_hash_common` which in turn calls `association :school`; right now, even though the Hash is being called with `build` rather than `create`, that `School` object still gets persisted. In fact, that behavior is *required* since we pass the `id` of the `School` object upstream to the `:pd_teacher_application` factory which as part of its own persistence logic expects its `school_id` attribute to reference a persisted object. Startingin FactoryBot 5, the default association strategy will now be inherited from the parent factory, so `build`ing a hash will `build` but not persist the associated `School` object. In most cases this is probably desirable, but here we are relying on the old strange behavior and so now need to explicitly specify that we always want to persist the associated `School` object. https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md#build-strategies-1
|
Upon further investigation, I found one more place where we need to update our use of School factories for FactoryBot v5, and learned about the potential for several more. Un-marking this PR as ready for review until I'm confident I've tracked down all of 'em |
pd_teacher_application_hash_common Factory…s to be persisted
…so that its associated records are also persisted and able to be validated
|
|
||
| test 'extract school and teacher info when they are empty' do | ||
| enrollment = build :pd_enrollment, school_info: (build :school_info_us) | ||
| enrollment = build( |
There was a problem hiding this comment.
Why are we using create on line 168 but build here?
There was a problem hiding this comment.
Because it this case the existing code cares about the school_info object being built but not created, whereas the other one doesn't have any such restrictions. And also because saving a Rails object will also save its associated records; using create here will also attempt to persist school_info
| enrollment = build :pd_enrollment, school_info: (build :school_info_us) | ||
| enrollment = build( | ||
| :pd_enrollment, | ||
| # Don't persist school info; it's too empty to pass validation. |
There was a problem hiding this comment.
What do you mean by "it's too empty to pass validation"?
There was a problem hiding this comment.
This test is explicitly calling build :school_info_us rather than just using the default school_info factory because the default factory also creates associated school data and other stuff that's required for the model to pass validation, but this test explicitly wants to check the serializer behavior when school and teacher info is empty.
davidsbailey
left a comment
There was a problem hiding this comment.
moving from build to create when possible makes sense to me!
In preparation for an update to FactoryBot 5, which fixes some unintuitive behavior when
building objects with descendants. Specifically, before, when creating records withbuild, any associated records created as a result would unexpectedly be persisted withcreaterather than just initialized withbuild.In fact, for the
Schoolmodel specifically, that behavior is required, in a couple of ways: in the:pd_teacher_applicationfactory, we pass theidof theSchoolobject upstream to the:pd_teacher_applicationfactory which as part of its own persistence logic expects itsschool_idattribute to reference a persisted object. And in the School model itself, because it doesn't use a standard Rails auto-incrementing integer id, we manually assign an id based on the latest persisted value; that means that we always want to persist any built School record before building another one, or we'll get anActiveRecord::RecordNotUniqueerror.Starting in FactoryBot 5, the default association strategy will now be inherited from the parent factory, so
building a hash willbuildbut not persist the associatedSchoolobject. In most cases this is probably desirable, but here we are relying on the old strange behavior and so now need to explicitly specify that we always want to persist the associatedSchoolobject.In a couple other places, we were implicitly (and I think accidentally) depending on this behavior to allow us to
buildand then validate records which require their associated records to be persisted in order to pass validation. In those cases, the fix was either tocreaterather thanbuildthe original record in the first place, or to explicitlycreatethe associated records we want to be persisted.Links
Testing story
Without this change, we get the following error when trying to run tests on FactoryBot 5:
With this change, the test passes.
We also had two tests that were just validating internal FactoryBot persistence logic. One of them is no longer accurate because we actually now are persisting some of these dependencies consistently, and the other one is just a bit unnecessary. Remove them both.
Follow-up Work
Update FactoryBot to v5