Skip to content

Enhance HasSortableRelations for inline drag-and-drop sorting#235

Merged
LukeTowers merged 3 commits into
developfrom
wip/sortable-relations
Jun 19, 2026
Merged

Enhance HasSortableRelations for inline drag-and-drop sorting#235
LukeTowers merged 3 commits into
developfrom
wip/sortable-relations

Conversation

@LukeTowers

@LukeTowers LukeTowers commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added support for deferred updates to sortable relation ordering.
    • Extended automatic sort order support to additional relation types.
  • Improvements

    • Enhanced public API for querying sortable relations.
    • Improved automatic sort order assignment during data binding.
  • Bug Fixes

    • Fixed module import issue.
  • Tests

    • Significantly expanded test coverage for sortable relation functionality.

Supports the Winter CMS inline list/relation reordering feature
(wintercms/winter#1472):

- Fix incorrect `Winter\Sorm` namespace import typo
- Make `getSortableRelations()` public and add `isSortableRelation()`
- Add a `$sessionKey` parameter to `setRelationOrder()` so reordering can
  write to `deferred_bindings.pivot_data` when a relation is deferred
- Default empty `$itemOrders` to a sequential 1..N range (instead of using
  the record ids as order values)
- Inject a pivot-table-qualified `order` clause and the pivot sort column
  for belongsToMany, morphToMany and morphedByMany relations
- Auto-append uses `max(sort_order) + 1` via the pivot query so a defined
  order clause cannot skew the result and non-contiguous orders are handled
- Preserve an explicit sort order (including 0) on attach, so committing
  deferred bindings that carry a pivot sort order no longer overwrites it

Adds DB-backed tests and a SortableArticle fixture covering attach
ordering, reordering, deferred writes, zero/explicit orders and the
pivot/order injection for all three pivot relation types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LukeTowers

Copy link
Copy Markdown
Member Author

Companion Winter CMS PR: wintercms/winter#1491 (implements the list/relation reordering UI on top of these trait changes). Tracking issue: wintercms/winter#1472.

LukeTowers added a commit to wintercms/winter that referenced this pull request Jun 19, 2026
Implements inline reordering of records directly in a List widget and in
RelationController relation lists, closing a longstanding request
(#1472). Requires the companion Storm changes
(wintercms/storm#235).

Lists widget:
- New `sortable` / `sortOrderColumn` config. When enabled, a drag-handle
  column is shown, column-header sorting is disabled (every column is
  forced non-sortable so `getSortColumn()` returns false and the model /
  relation's own order is preserved), and pagination is disabled so every
  record is shown in order.
- `onReorder()` AJAX handler validates that submitted record ids are within
  the current query scope, then fires a `list.reorder` event.
- `getRecordSortOrder()` reads the sort value from a record via the
  configured column path (e.g. `sort_order` or `pivot[sort_order]`).

ListController:
- `sortable: true` in the list config validates the model uses the Sortable
  trait and binds `list.reorder` to `setSortableOrder()`.

RelationController:
- `view[sortable]: true` validates the parent uses HasSortableRelations and
  declares the relation, then binds `list.reorder` to `setRelationOrder()`
  (passing the session key in deferred mode).
- In deferred mode `withDeferred()` builds the query in orphan mode with no
  pivot join, so the pivot order clause is stripped and the records are
  ordered in PHP from `deferred_bindings.pivot_data` (overlaid on any
  committed pivot rows) via a `list.extendRecords` binding. The Lists widget
  stays relation-agnostic.

Assets: minimal, additive SortableJS initializer (the existing jQuery list
widget is untouched) plus styles; SortableJS is vendored at
`js/lib/sortable.min.js` and added to package.json.

Tests: ListsSortableTest covers the sortable widget config, drag-handle
column, `getRecordSortOrder` (direct and pivot paths), and `onReorder`
event firing + query-scope validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bennothommo pushed a commit to wintercms/wn-backend-module that referenced this pull request Jun 19, 2026
Implements inline reordering of records directly in a List widget and in
RelationController relation lists, closing a longstanding request
(wintercms/winter#1472). Requires the companion Storm changes
(wintercms/storm#235).

Lists widget:
- New `sortable` / `sortOrderColumn` config. When enabled, a drag-handle
  column is shown, column-header sorting is disabled (every column is
  forced non-sortable so `getSortColumn()` returns false and the model /
  relation's own order is preserved), and pagination is disabled so every
  record is shown in order.
- `onReorder()` AJAX handler validates that submitted record ids are within
  the current query scope, then fires a `list.reorder` event.
- `getRecordSortOrder()` reads the sort value from a record via the
  configured column path (e.g. `sort_order` or `pivot[sort_order]`).

ListController:
- `sortable: true` in the list config validates the model uses the Sortable
  trait and binds `list.reorder` to `setSortableOrder()`.

RelationController:
- `view[sortable]: true` validates the parent uses HasSortableRelations and
  declares the relation, then binds `list.reorder` to `setRelationOrder()`
  (passing the session key in deferred mode).
- In deferred mode `withDeferred()` builds the query in orphan mode with no
  pivot join, so the pivot order clause is stripped and the records are
  ordered in PHP from `deferred_bindings.pivot_data` (overlaid on any
  committed pivot rows) via a `list.extendRecords` binding. The Lists widget
  stays relation-agnostic.

Assets: minimal, additive SortableJS initializer (the existing jQuery list
widget is untouched) plus styles; SortableJS is vendored at
`js/lib/sortable.min.js` and added to package.json.

Tests: ListsSortableTest covers the sortable widget config, drag-handle
column, `getRecordSortOrder` (direct and pivot paths), and `onReorder`
event firing + query-scope validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LukeTowers LukeTowers requested a review from Copilot June 19, 2026 04:46
@LukeTowers LukeTowers marked this pull request as ready for review June 19, 2026 04:46
@wintercms wintercms deleted a comment from coderabbitai Bot Jun 19, 2026

This comment was marked as resolved.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@LukeTowers, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 52 minutes and 49 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c8acaa8d-d280-4d55-a815-3fb1ed2fb89c

📥 Commits

Reviewing files that changed from the base of the PR and between 680444b and 4d375d5.

📒 Files selected for processing (1)
  • src/Database/Traits/HasSortableRelations.php

Walkthrough

HasSortableRelations gains a ?string $sessionKey parameter on setRelationOrder, enabling deferred pivot updates by writing pivot_data[$column] on matching DeferredBinding records rather than live pivot rows. The afterAttach handler now skips auto-appending when the attached pivot row already carries an explicit sort-order value. Initialization is extended to inject the pivot column and default order clause for morphedByMany relations in addition to belongsToMany and morphToMany. getSortableRelations() is promoted from protected to public, a new isSortableRelation() helper is added, a namespace typo (Winter\SormWinter\Storm) is corrected, and updateRelationOrder accepts a nullable order. A new SortableArticle fixture and a fully rewritten HasSortableRelationsTest covering all added behaviors accompany the changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Drag-and-Drop Sorting for Lists & RelationControllers winter#1472: This PR implements the foundational database-level enhancements listed as prerequisites in that issue, including the sessionKey parameter for deferred bindings, public getSortableRelations(), the new isSortableRelation() helper, and auto-injection of order clauses into pivot relation definitions.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main enhancement - adding support for inline drag-and-drop sorting functionality to the HasSortableRelations trait through multiple API and behavioral improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wip/sortable-relations

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 and usage tips.

Comment thread src/Database/Traits/HasSortableRelations.php Outdated
@LukeTowers LukeTowers merged commit 66e600f into develop Jun 19, 2026
12 of 13 checks passed
@LukeTowers LukeTowers deleted the wip/sortable-relations branch June 19, 2026 05:56
LukeTowers added a commit to wintercms/docs that referenced this pull request Jun 19, 2026
LukeTowers added a commit to wintercms/winter that referenced this pull request Jun 19, 2026
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Closes: #1472
Related: wintercms/storm#235, wintercms/docs#260
bennothommo pushed a commit to wintercms/wn-backend-module that referenced this pull request Jun 19, 2026
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Closes: wintercms/winter#1472
Related: wintercms/storm#235, wintercms/docs#260
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