Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consistently Persist PD FactoryBot Records in v5 #51341

Merged
merged 6 commits into from
Apr 20, 2023

Conversation

Hamms
Copy link
Contributor

@Hamms Hamms commented Apr 14, 2023

In preparation for an update to FactoryBot 5, which fixes some unintuitive behavior when building objects with descendants. Specifically, before, when creating records with build, any associated records created as a result would unexpectedly be persisted with create rather than just initialized with build.

In fact, for the School model specifically, that behavior is required, in a couple of ways: in the :pd_teacher_application factory, 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. 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 an ActiveRecord::RecordNotUnique error.

Starting in FactoryBot 5, the default association strategy will now be inherited from the parent factory, so building 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.

In a couple other places, we were implicitly (and I think accidentally) depending on this behavior to allow us to build and then validate records which require their associated records to be persisted in order to pass validation. In those cases, the fix was either to create rather than build the original record in the first place, or to explicitly create the 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:

ERROR["test_deleting_user_deletes_dependent_pd_applications", "UserTest", 21.59177164407447]
 test_deleting_user_deletes_dependent_pd_applications#UserTest (21.59s)
Minitest::UnexpectedError:         ActiveRecord::RecordNotFound: Couldn't find School with 'id'=60001411747
            app/models/school_info.rb:98:in `sync_from_schools'
            lib/school_info_deduplicator.rb:20:in `get_duplicate_school_info'
            app/models/pd/application/teacher_application.rb:105:in `update_user_school_info!'
            test/models/user_test.rb:3156:in `block in <class:UserTest>'
            test/testing/setup_all_and_teardown_all.rb:36:in `run'

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

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
@Hamms Hamms marked this pull request as ready for review April 14, 2023 19:33
@Hamms Hamms requested review from a team April 14, 2023 19:33
Copy link
Contributor

@megcrenshaw megcrenshaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this, Elijah!

@Hamms Hamms marked this pull request as draft April 18, 2023 23:33
@Hamms
Copy link
Contributor Author

Hamms commented Apr 18, 2023

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

@Hamms Hamms changed the title Always Persist School in pd_teacher_application_hash_common Factory Consistently Persist FactoryBot Schools in v5 Apr 18, 2023
@Hamms Hamms changed the title Consistently Persist FactoryBot Schools in v5 Consistently Persist PD FactoryBot Records in v5 Apr 19, 2023
@Hamms Hamms added the Ruby Update Everything related to work to update the version of Ruby our codebase runs on label Apr 19, 2023
@Hamms Hamms marked this pull request as ready for review April 19, 2023 23:25
@Hamms Hamms requested review from megcrenshaw and a team April 19, 2023 23:26
@@ -178,7 +178,13 @@ class Api::V1::Pd::EnrollmentFlatAttendanceSerializerTest < ::ActionController::
end

test 'extract school and teacher info when they are empty' do
enrollment = build :pd_enrollment, school_info: (build :school_info_us)
enrollment = build(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using create on line 168 but build here?

Copy link
Contributor Author

@Hamms Hamms Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "it's too empty to pass validation"?

Copy link
Contributor Author

@Hamms Hamms Apr 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, makes sense. Thank you!

Copy link
Member

@davidsbailey davidsbailey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moving from build to create when possible makes sense to me!

@Hamms Hamms merged commit b0ee0f2 into staging Apr 20, 2023
2 checks passed
@Hamms Hamms deleted the pd-factory-persist-school branch April 20, 2023 20:31
@Hamms Hamms mentioned this pull request Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ruby Update Everything related to work to update the version of Ruby our codebase runs on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants