Skip to content

Resolve nested relations against the innermost ancestor - #1530

Merged
mjauvin merged 1 commit into
wintercms:nested-relation-controllerfrom
AIC-BV:fix/nested-relation-ambient-ancestor
Aug 26, 2026
Merged

Resolve nested relations against the innermost ancestor#1530
mjauvin merged 1 commit into
wintercms:nested-relation-controllerfrom
AIC-BV:fix/nested-relation-ambient-ancestor

Conversation

@AIC-BV

@AIC-BV AIC-BV commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Targets the nested-relation-controller branch, not develop — a follow-up to #1499 (details in this comment).

Problem

resolveNestedContext()'s ambient branch trusts the model passed to initRelation() as the relation's parent, per its docblock ("$model is trusted as-is - it's the widget's own bound model"). That model is $this->model, set from the enclosing relation, because the call arrives as relationRender() → validateField() → initRelation($this->model).

At one level of nesting the enclosing record is the parent, so it works. At two it is the grandparent, and the leaf relation resolves against the wrong model:

BadMethodCallException: Call to undefined method Aic\Account\Models\Closet::closet()

#6 RelationController.php(450): Model->__call('closet', Array)
#7 RelationController.php(304): RelationController->initRelation(Object(Closet), 'closet')
#8 RelationController.php(931): RelationController->validateField('closet')
#9 RelationController->relationRender('closet')

Reproduced on a real three-level config (Order → closets → paslatten → closet), where the paslat form's own partial contains a single $this->relationRender('closet')closet being a relation on Paslatten, not on Closet.

Fix

Walk the ambient path's hops from the root, exactly as the reconstructed branch two lines below already does:

 if ($ambientPath !== null) {
     $this->nestedField = $ambientPath . '[' . $field . ']';
-    return [$model, $field, true];
+    [$hops] = $this->parseBracketField($this->nestedField);
+
+    return [$this->resolveModelForHops($this->rootModel, $hops), $field, true];
 }

Both cases now share one resolution mechanism, and the ambient path gains resolveModelForHops()'s $relation->find($id) parent check at every depth rather than only on reconstructed paths. The docblock is corrected to match.

Tests

Adds testAmbientFieldTwoLevelsDeepResolvesInnermostAncestorNotEnclosingRecord, built on the branch's existing self-referencing Item → items → Item fixture. It fails without the change (resolves Item A instead of the sub-item) and passes with it.

  • The branch's 22 existing nested tests pass unmodified — depth 2 resolves to the same model either way, which is also why the suite did not catch this.
  • Full modules/backend suite run with and without the change, failures diffed: the only delta is the new test flipping to pass. (43 pre-existing errors in UserTest/UserAuthorizationTest in my environment, unrelated.)
  • Verified in a browser afterwards: closet A's paslat renders closet A, closet B's renders closet B. Two-level create, update, delete and in-place list refresh all still behave.

resolveNestedContext()'s ambient branch trusted the model handed to
initRelation() as the relation's parent. That model is $this->model,
set from the ENCLOSING relation, because the call arrives as
relationRender() -> validateField() -> initRelation($this->model).

At one level of nesting the enclosing record is the parent, so this
works. At two it is the grandparent, and the leaf relation resolves
against the wrong model - in practice a BadMethodCallException, since
the grandparent's class usually has no such relation.

Walk the ambient path's hops from the root instead, exactly as the
reconstructed branch below already does. Both cases now share one
resolution mechanism, and the ambient path gains resolveModelForHops()'s
$relation->find($id) parent check at every depth.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d5079809-bdb3-48f9-9d1e-37e902f89b48

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mjauvin
mjauvin merged commit 416c7a0 into wintercms:nested-relation-controller Aug 26, 2026
15 checks passed
@mjauvin

mjauvin commented Aug 26, 2026

Copy link
Copy Markdown
Member

Thanks, will test this.

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.

2 participants