Skip to content

Unwrap an Option assigned to a singular association writer - #65

Merged
nz merged 3 commits into
nz/optional-readers-modulefrom
nz/association-writers-unwrap
Sep 8, 2026
Merged

nz merged 3 commits into
nz/optional-readers-modulefrom
nz/association-writers-unwrap

Conversation

@nz

@nz nz commented Sep 8, 2026

Copy link
Copy Markdown
Member

Closes #63. Stacked on #64.

Assigning an Option to a belongs_to or has_one writer raised AssociationTypeMismatch (and None() too, since the writer tests truthiness rather than nil?), which broke the common other.author = book.author idiom whenever the source reader is wrapped. Strictness belongs on the reading side; on assignment it is ceremony. Singular association writers now coerce: Some(record) assigns as the record, None() as nil, one layer only. A Some of the wrong class still raises the type mismatch, naming the inner class.

One prepend on ActiveRecord::Associations::SingularAssociation#writer covers belongs_to, polymorphic belongs_to, has_one, and has_one :through: every generated assoc= reaches writer, and no subclass overrides it (the subclasses override replace, which is why a prepend there was not enough). Collection writers, build_/create_, nested attributes, where, and foreign-key _id writers are untouched.

The compromises register stays at five: item 3 now covers this boundary alongside quoting and predicate building.

Verified locally: rubocop (no new offenses), yard doctest, rake test, nix build .#errgonomic, nix flake check --all-systems.

nz added 3 commits September 8, 2026 16:57
An association writer looks like assignment to a typed field but is a
setter, so it takes what the wrapped reader on another record hands
back: Some(record) assigns the record, None() clears the association.
Strictness stays on the reading side, where an absent value is the
error to catch.

Both macros reach SingularAssociation#writer, which neither
BelongsToAssociation nor HasOneAssociation overrides, so one prepend
covers them. Unwrapping there leaves the association's own type check
in place, and a Some of the wrong class raises naming the class
inside it.
The register is closed at five, and unwrapping on assignment is the
same boundary coercion as the SQL-side unwrap rather than a sixth
compromise, so item 3 names both boundaries.
Both reach the same writer, and both read the assigned record rather
than only its key: a polymorphic belongs_to takes the foreign type
from the record's class, and a has_one :through writes the join row
from the record's own primary key. A polymorphic writer accepts any
class, so it has no type check to reject a wrapper and instead fails
reaching into it for a primary key.
@nz
nz force-pushed the nz/association-writers-unwrap branch from 520f5b7 to 2976244 Compare September 8, 2026 21:58
@nz
nz merged commit b8f9a84 into nz/optional-readers-module Sep 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant