fix: preserve input order in bulk manage_relationship#2639
fix: preserve input order in bulk manage_relationship#2639zachdaniel merged 4 commits intoash-project:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes ordering of records returned from manage_relationship when creating multiple related records in bulk, ensuring the returned list preserves the input order across data layers where bulk insert return order is not guaranteed.
Changes:
- Add
sorted?: trueto theAsh.bulk_create/4call in thedo_batch_create(has_many) path to preserve input ordering. - Align has_many bulk-create behavior with existing many_to_many bulk-create logic that already uses
sorted?: true.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
This PR fixes ordering issues in bulk_manage_relationship so that results preserve the original input order even when creates and updates are interleaved.
Changes:
- Ensure
Ash.bulk_create/3returns records in input order by addingsorted?: trueto the has_many batch create path. - Preserve original input order across mixed create/update batches by tagging managed records with their input index and sorting by that index instead of relying on
Enum.reverse/1. - Add a regression test that fails when interleaved creates/updates don’t preserve order.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
lib/ash/actions/managed_relationships.ex |
Tags managed records with input indices, sorts results by index (vs reverse), and adds sorted?: true to bulk creates to preserve order. |
test/manage_relationship_test.exs |
Adds a regression test asserting interleaved creates/updates preserve the user-provided order. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Let's check |
|
The motivation here is that without this, the result returned from an update can have a different order than the input, which causes issues in forms — e.g., a user submits relationship items in order [A, B, C, D], but the returned result comes back as [B, D, A, C], causing the UI to reorder items unexpectedly on re-render. That said, I'm happy to open a 4.0 issue for making this opt-in if you still think it's worth it. Let me know! |
|
Its probably fine to have as is but in general we have sorted outputs as an opt-in feature which allows us to freely change the internals without having to preserve ordering, so changing it in 4.0 to not guarantee order would make sense IMO. But you're also right the cost is probably not significant enough. |
|
Looks like |
Contributor checklist
Leave anything that you believe does not apply unchecked.
Summary
sorted?: truetoAsh.bulk_createcalls indo_batch_createto preserve order within create results (thedo_batch_create_m2mpath already had this)bulk_manage_relationshipprocesses a mix of creates and updates — they are batched separately (creates first, updates second), which breaks the original input order regardless ofsorted?Problem
There are two ordering issues in the bulk manage_relationship path:
1. Missing
sorted?: trueindo_batch_createAfter the sequential → bulk_create refactoring, the has_many path was missing
sorted?: true. Without it,bulk_createmay return records in a different order than the input.2. Creates and updates processed in separate batches
bulk_manage_relationshipclassifies inputs into creates and updates, then processes creates first, updates second. Each result is prepended via[record | acc], and the caller doesEnum.reverseat the end. This means the final order is always creates before updates, regardless of original input order.For example, input
[existing, new1, new2]produces[new1, new2, existing].The sequential path processes inputs one-by-one in order, so it doesn't have this problem.
Both issues are reproducible with the ETS data layer — the added regression test demonstrates this.
Solution
sorted?: true— Added toAsh.bulk_createcalls so create results match input order.new_value(struct inputs, already_created, bulk_create results, m2m join, sequential fallback, updates, lookups) tags it with__manage_relationship_index__metadata viatag_input_index/2.Enum.reverse.