@@ -963,28 +963,30 @@ defmodule Ash.Actions.ManagedRelationships do
963963 opts ,
964964 notifications
965965 ) ,
966- creates <- additional_creates ++ creates ,
967- # Batch creates
966+ # Apply updates before batch-creating new related records. This preserves
967+ # the relative ordering of already-present vs newly-created items for
968+ # relationship lists backed by order-sensitive data layers (e.g. ETS).
968969 { :ok , new_value , notifications } <-
969- batch_creates (
970- creates ,
970+ process_updates (
971+ updates ,
971972 record ,
972973 new_value ,
973974 relationship ,
974975 changeset ,
975976 actor ,
976- index ,
977+ pkeys ,
977978 opts ,
978979 notifications
979980 ) do
980- process_updates (
981- updates ,
981+ # Batch creates
982+ batch_creates (
983+ additional_creates ++ creates ,
982984 record ,
983985 new_value ,
984986 relationship ,
985987 changeset ,
986988 actor ,
987- pkeys ,
989+ index ,
988990 opts ,
989991 notifications
990992 )
@@ -1387,10 +1389,13 @@ defmodule Ash.Actions.ManagedRelationships do
13871389 end )
13881390
13891391 inputs_to_bulk_create =
1390- Enum . map ( map_inputs_with_joins , fn { { input , join_params } , _ } ->
1391- { input , join_params }
1392+ Enum . map ( map_inputs_with_joins , fn { { input , join_params } , { _orig_input , input_index } } ->
1393+ { input , join_params , input_index }
13921394 end )
13931395
1396+ input_indices_to_bulk_create =
1397+ Enum . map ( inputs_to_bulk_create , fn { _ , _ , input_index } -> input_index end )
1398+
13941399 if inputs_to_bulk_create == [ ] do
13951400 # Only struct inputs - just create join records
13961401 create_join_records_for_m2m (
@@ -1406,7 +1411,7 @@ defmodule Ash.Actions.ManagedRelationships do
14061411 )
14071412 else
14081413 # Bulk create destination records
1409- bulk_inputs = Enum . map ( inputs_to_bulk_create , fn { input , _ } -> input end )
1414+ bulk_inputs = Enum . map ( inputs_to_bulk_create , fn { input , _ , _ } -> input end )
14101415
14111416 bulk_context =
14121417 Map . take ( changeset . context , [ :shared ] )
@@ -1446,7 +1451,7 @@ defmodule Ash.Actions.ManagedRelationships do
14461451 created_with_joins =
14471452 Enum . zip (
14481453 created_records || [ ] ,
1449- Enum . map ( inputs_to_bulk_create , fn { _ , join } -> join end )
1454+ Enum . map ( inputs_to_bulk_create , fn { _ , join , _ } -> join end )
14501455 )
14511456 |> Enum . map ( fn { created , join_params } -> { created , join_params } end )
14521457
@@ -1469,7 +1474,28 @@ defmodule Ash.Actions.ManagedRelationships do
14691474 error = List . first ( List . wrap ( errors ) )
14701475
14711476 if error do
1472- { :error , add_bread_crumb ( error , relationship , :create ) }
1477+ # Bulk create errors don't carry the manage_relationship input index
1478+ # in a way AshPhoenix can route the nested errors. Reattach the
1479+ # index based on the position in the bulk inputs we sent.
1480+ bulk_index =
1481+ error
1482+ |> Map . get ( :meta )
1483+ |> case do
1484+ % { bulk_create_index: idx } -> idx
1485+ _ -> nil
1486+ end || Map . get ( error , :bulk_create_index ) || 0
1487+
1488+ input_index = Enum . at ( input_indices_to_bulk_create , bulk_index ) || 0
1489+
1490+ relationship_error_key =
1491+ opts
1492+ |> Keyword . get ( :meta , [ ] )
1493+ |> Keyword . get ( :id ) || relationship . name
1494+
1495+ error
1496+ |> add_bread_crumb ( relationship , :create )
1497+ |> Ash.Error . set_path ( [ relationship_error_key , input_index ] )
1498+ |> then ( & { :error , & 1 } )
14731499 else
14741500 { :error ,
14751501 add_bread_crumb (
0 commit comments