Skip to content

Commit 0bfd045

Browse files
committed
improvement: add backwards compatibility for data layers returning only index metadata
1 parent dbc9d9f commit 0bfd045

File tree

6 files changed

+101
-22
lines changed

6 files changed

+101
-22
lines changed

lib/ash/actions/create/bulk.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,7 @@ defmodule Ash.Actions.Create.Bulk do
14561456
all_changes,
14571457
results,
14581458
changesets_by_index,
1459+
nil,
14591460
changesets,
14601461
opts,
14611462
ref,

lib/ash/actions/destroy/bulk.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2171,6 +2171,7 @@ defmodule Ash.Actions.Destroy.Bulk do
21712171
all_changes,
21722172
batch,
21732173
changesets_by_index,
2174+
nil,
21742175
changesets,
21752176
opts,
21762177
ref,

lib/ash/actions/update/bulk.ex

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,13 +2147,15 @@ defmodule Ash.Actions.Update.Bulk do
21472147
end
21482148
end
21492149

2150-
defp index_changesets_by_ref(batch, context_key) do
2151-
Enum.reduce(batch, %{}, fn changeset, changesets_by_ref ->
2152-
Map.put(
2153-
changesets_by_ref,
2154-
changeset.context[context_key].ref,
2155-
changeset
2156-
)
2150+
defp index_changesets(batch, context_key) do
2151+
Enum.reduce(batch, {%{}, %{}}, fn changeset, {by_ref, by_index} ->
2152+
ref = changeset.context[context_key].ref
2153+
index = changeset.context[context_key].index
2154+
2155+
{
2156+
Map.put(by_ref, ref, changeset),
2157+
Map.put(by_index, index, ref)
2158+
}
21572159
end)
21582160
end
21592161

@@ -2529,7 +2531,7 @@ defmodule Ash.Actions.Update.Bulk do
25292531
end
25302532
end)
25312533

2532-
changesets_by_ref = index_changesets_by_ref(batch, context_key)
2534+
{changesets_by_ref, changesets_by_index} = index_changesets(batch, context_key)
25332535

25342536
batch =
25352537
batch
@@ -2686,7 +2688,7 @@ defmodule Ash.Actions.Update.Bulk do
26862688
end)
26872689
end
26882690

2689-
{batch, changesets_by_ref}
2691+
{batch, changesets_by_ref, changesets_by_index}
26902692
end
26912693

26922694
defp manage_relationships(updated, domain, changeset, engine_opts) do
@@ -2704,7 +2706,7 @@ defmodule Ash.Actions.Update.Bulk do
27042706
end
27052707

27062708
defp run_after_action_hooks(
2707-
{batch_results, changesets_by_ref},
2709+
{batch_results, changesets_by_ref, changesets_by_index},
27082710
opts,
27092711
domain,
27102712
ref,
@@ -2759,11 +2761,11 @@ defmodule Ash.Actions.Update.Bulk do
27592761
end
27602762
end)
27612763

2762-
{results, changesets_by_ref}
2764+
{results, changesets_by_ref, changesets_by_index}
27632765
end
27642766

27652767
defp process_results(
2766-
{batch, changesets_by_ref},
2768+
{batch, changesets_by_ref, changesets_by_index},
27672769
changes,
27682770
all_changes,
27692771
opts,
@@ -2780,6 +2782,7 @@ defmodule Ash.Actions.Update.Bulk do
27802782
all_changes,
27812783
batch,
27822784
changesets_by_ref,
2785+
changesets_by_index,
27832786
changesets,
27842787
opts,
27852788
ref,
@@ -2872,6 +2875,7 @@ defmodule Ash.Actions.Update.Bulk do
28722875
all_changes,
28732876
results,
28742877
changesets_by_ref,
2878+
changesets_by_index,
28752879
changesets,
28762880
opts,
28772881
ref,
@@ -2964,6 +2968,7 @@ defmodule Ash.Actions.Update.Bulk do
29642968
result,
29652969
changes[index],
29662970
changesets_by_ref,
2971+
changesets_by_index,
29672972
metadata_key,
29682973
ref_metadata_key
29692974
)
@@ -3573,10 +3578,26 @@ defmodule Ash.Actions.Update.Bulk do
35733578
changeset
35743579
end
35753580

3576-
defp result_matches_changes?(result, changes, changesets_by_ref, metadata_key, ref_metadata_key) do
3581+
defp result_matches_changes?(
3582+
result,
3583+
changes,
3584+
changesets_by_ref,
3585+
changesets_by_index,
3586+
metadata_key,
3587+
ref_metadata_key
3588+
) do
35773589
if ref_metadata_key do
35783590
ref_key = result.__metadata__[ref_metadata_key]
35793591

3592+
ref_key =
3593+
if ref_key do
3594+
ref_key
3595+
else
3596+
# Fallback: data layer didn't return ref, look it up by index
3597+
index = result.__metadata__[metadata_key]
3598+
changesets_by_index[index]
3599+
end
3600+
35803601
changesets_by_ref
35813602
|> Map.get(ref_key)
35823603
|> ensure_changeset!(result, metadata_key, ref_metadata_key)

lib/ash/data_layer/ets/ets.ex

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,15 +1880,23 @@ defmodule Ash.DataLayer.Ets do
18801880
{:ok, result} ->
18811881
result =
18821882
if result_changeset.context[:bulk_update] do
1883-
result
1884-
|> Ash.Resource.put_metadata(
1885-
:bulk_update_index,
1886-
result_changeset.context.bulk_update.index
1887-
)
1888-
|> Ash.Resource.put_metadata(
1889-
:bulk_action_ref,
1890-
result_changeset.context.bulk_update.ref
1891-
)
1883+
result =
1884+
result
1885+
|> Ash.Resource.put_metadata(
1886+
:bulk_update_index,
1887+
result_changeset.context.bulk_update.index
1888+
)
1889+
1890+
# For testing backwards compatibility, allow skipping ref metadata
1891+
if Application.get_env(:ash, :test_bulk_index_only, false) do
1892+
result
1893+
else
1894+
Ash.Resource.put_metadata(
1895+
result,
1896+
:bulk_action_ref,
1897+
result_changeset.context.bulk_update.ref
1898+
)
1899+
end
18921900
else
18931901
result
18941902
end

test/actions/bulk/bulk_update_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,35 @@ defmodule Ash.Test.Actions.BulkUpdateTest do
964964
end)
965965
end
966966

967+
test "runs after batch hooks with legacy data layers (no refs)" do
968+
assert %Ash.BulkResult{
969+
records: [
970+
%{title: "before_title1_after", title2: "updated value"},
971+
%{title: "before_title2_after", title2: "updated value"}
972+
]
973+
} =
974+
Ash.bulk_create!([%{title: "title1"}, %{title: "title2"}], Post, :create,
975+
return_stream?: true,
976+
return_records?: true,
977+
authorize?: false
978+
)
979+
|> Stream.map(fn {:ok, result} ->
980+
result
981+
end)
982+
|> Enum.to_list()
983+
|> Enum.map(&Ash.Test.Helpers.strip_bulk_action_ref/1)
984+
|> Ash.bulk_update!(:update_with_after_batch, %{title2: "updated value"},
985+
resource: Post,
986+
strategy: :stream,
987+
return_records?: true,
988+
return_errors?: true,
989+
authorize?: false
990+
)
991+
|> Map.update!(:records, fn records ->
992+
Enum.sort_by(records, & &1.title)
993+
end)
994+
end
995+
967996
test "runs changes in batches" do
968997
create_records = fn count ->
969998
Stream.iterate(1, &(&1 + 1))

test/support/helpers.ex

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,23 @@ defmodule Ash.Test.Helpers do
3636
})
3737
end
3838
end
39+
40+
@doc """
41+
Strips bulk_action_ref metadata from records to simulate legacy data layer behavior.
42+
43+
Use this in tests that verify backwards compatibility with data layers that don't
44+
support returning the bulk action ref.
45+
"""
46+
def strip_bulk_action_refs(%Ash.BulkResult{records: records} = result)
47+
when is_list(records) do
48+
%{result | records: Enum.map(records, &strip_bulk_action_ref/1)}
49+
end
50+
51+
def strip_bulk_action_refs(result), do: result
52+
53+
def strip_bulk_action_ref(record) when is_struct(record) do
54+
Ash.Resource.set_metadata(record, Map.delete(record.__metadata__, :bulk_action_ref))
55+
end
56+
57+
def strip_bulk_action_ref(other), do: other
3958
end

0 commit comments

Comments
 (0)