Read a has_one as an Option - #53
Merged
Merged
Conversation
A has_one is absent whenever no row points back at the record, which is the same absence a nullable column carries, so a converted model that returned a bare nil from half its readers made the surface inconsistent: a caller had to know which kind of absence each reader spoke. required: true is left alone. It asserts the record is there, so absence is a validation failure rather than a value to handle.
accepts_nested_attributes_for assigns through the public reader and asks whatever it finds there whether it is a new record. A None answers nil? but is not nil, so it reached that question and raised, taking out build, update and destroy for the association — on an optional belongs_to as much as on a has_one. ActiveRecord needs a bare nil here, the way it needs the raw value behind encrypts, so this takes the same shape: the association is recorded as an exclusion and keeps its plain reader.
This was referenced Aug 12, 2026
nz
enabled auto-merge
August 12, 2026 21:12
Leave a singular association with nested attributes unwrapped
allizad
approved these changes
Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of the stack, on top of #52.
nz/nested-attributes-unwrapandnz/cheap-recursion-guardfollow.A has_one carries the same absence a nullable column does
A
has_oneis absent whenever no row points back at the record, which is the same kind of absence a nullable column carries. Before this, a converted model answered in two dialects: an Option from its columns and itsoptional: truebelongs_to, and a barenilfrom itshas_one. A caller had to know which reader spoke which, and the compromise the concern documents —None#nil?answers true so ordinary nil checks keep working — quietly papered over the difference until something reached for a combinator.required: trueis the exception. It asserts the record is there, so absence is a validation failure rather than a value for a caller to handle, and the reader stays plain.Absence being representable does not excuse breaking the association
The wrapped reader has to keep the association's own machinery working, so the tests cover more than reading:
has_one, declared on either side of the includecreate_profile!and direct assignment followed bysave!dependent: :destroystill destroying the childrequired: truehas_onestaying unwrapped, and still failing validation when its record is missingaccepts_nested_attributes_foron ahas_onedoes not survive this, for the same reason it does not survive on anoptional: true belongs_to: ActiveRecord assigns through the public reader and asks the value whether it is a new record. That is a pre-existing defect rather than something this PR introduces, andnz/nested-attributes-unwrapis next in the stack because of it.Testing
rake— 27 tests, 71 assertions, and 118 doctests, all passing.