SIGN IN SIGN UP

Support non-null HasOne (#3118)

* Rename `Has*Model` to `ActiveHas*`

* Support non-null HasOne

* Fix E0034 ambiguity: unify HasOne::loaded / ActiveHasOne::set constructors

The per-cardinality inherent impls each defined a same-named associated
constructor (loaded / set), so the unqualified paths HasOne::loaded and
ActiveHasOne::set were ambiguous (E0034) at every call site — the E: EntityTrait
where-bound does not disambiguate associated-function path resolution. The crate's
own tests and doctests failed to compile as a result.

Define loaded / set once on the cardinality-generic impl, dispatching arg-wrapping
through IntoHasOneLoaded / IntoActiveHasOneSet helper traits (one impl for the
required E case, one for the optional Option<E> case). Bare model -> required,
Option<model> -> optional, resolved unambiguously.

Also fixes an entity_loader test that passed a bare model to a nullable relation
(cake.bakery is HasOne<Option<..>>), which the ambiguity had masked.

* Support nested writes for duplicate-target belongs_to; clear() no-op on unset FK

Two fixes to nested-ActiveModel save behavior:

1. belongs_to relations that share a target entity with another relation used to
   silently generate no save/detach code (the action codegen was gated on the
   target entity being unique), so their nested writes were no-ops. They now always
   generate: a belongs_to with an explicit relation_enum, or a non-unique target,
   is keyed by its relation variant via `set_parent_key_for` / `clear_parent_key_for`
   (the default variant is inferred the same way the loader does); a unique target
   keeps the entity-keyed path. Fixes e.g. `user_follower` (two belongs_to to `user`).

2. `clear_<field>()` on a nullable belongs_to whose FK column was never set (a
   freshly-built ActiveModel) returned `DbErr::AttrNotSet`; it is now a no-op, since
   an unset nullable FK is already effectively clear.

Adds `test_belongs_to_duplicate_target` and `test_clear_unset_belongs_to_is_noop`.

* Revert now-redundant turbofish to bare HasOne::loaded / ActiveHasOne::set

With the E0034 root-cause fix in place, the unqualified constructors resolve
unambiguously, so the per-site `::<Option<E>>::` turbofish is no longer needed.
Revert it in the doc example, lib test modules, into_active_model bodies, and the
integration tests so users see the clean bare-call form. The macro-generated code
keeps its explicit turbofish (internal, not user-facing).

* Fix ActiveModelEx optional belongs_to clearing

* Split HasOne and BelongsTo

---------

Co-authored-by: Chris Tsang <chris.2y3@outlook.com>
狐
狐狸 committed
e94a44b27a3a03a174cb15baceed6eb49ea59752
Parent: 324ea7b
Committed by Chris Tsang <chris.2y3@outlook.com> on 7/12/2026, 6:13:27 PM