Skip to content

Commit 07ac046

Browse files
committed
fix: update ash_postgresql to handle the new bulk_create response in Ash v3.5.44
1 parent c45b336 commit 07ac046

File tree

3 files changed

+221
-5
lines changed

3 files changed

+221
-5
lines changed

lib/data_layer.ex

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,11 +2047,18 @@ defmodule AshPostgres.DataLayer do
20472047
maybe_create_tenant!(resource, result)
20482048
end
20492049

2050-
Ash.Resource.put_metadata(
2051-
result,
2052-
:bulk_create_index,
2053-
changeset.context.bulk_create.index
2054-
)
2050+
case get_bulk_operation_metadata(changeset, :bulk_create) do
2051+
{index, metadata_key} ->
2052+
Ash.Resource.put_metadata(result, metadata_key, index)
2053+
2054+
nil ->
2055+
# Compatibility fallback
2056+
Ash.Resource.put_metadata(
2057+
result,
2058+
:bulk_create_index,
2059+
changeset.context[:bulk_create][:index]
2060+
)
2061+
end
20552062
end)}
20562063
end
20572064
end
@@ -3638,4 +3645,20 @@ defmodule AshPostgres.DataLayer do
36383645
resource
36393646
end
36403647
end
3648+
3649+
defp get_bulk_operation_metadata(changeset, bulk_action_type) do
3650+
changeset.context
3651+
|> Enum.find_value(fn
3652+
# New format: {{:bulk_create, ref}, value} -> {index, metadata_key}
3653+
{{^bulk_action_type, ref}, value} ->
3654+
{value.index, {:"#{bulk_action_type}_index", ref}}
3655+
3656+
# Fallback for old format: {:bulk_create, value} -> {index, metadata_key}
3657+
{^bulk_action_type, value} when is_map(value) ->
3658+
{value.index, :"#{bulk_action_type}_index"}
3659+
3660+
_ ->
3661+
nil
3662+
end)
3663+
end
36413664
end

test/bulk_create_test.exs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,147 @@ defmodule AshPostgres.BulkCreateTest do
355355
|> Ash.read!()
356356
end
357357
end
358+
359+
describe "nested bulk operations" do
360+
test "supports bulk_create in after_action callbacks" do
361+
result =
362+
Ash.bulk_create!(
363+
[%{title: "trigger_nested"}],
364+
Post,
365+
:create_with_nested_bulk_create,
366+
return_records?: true,
367+
authorize?: false
368+
)
369+
370+
# Assert the bulk result contains the expected data
371+
assert %Ash.BulkResult{records: [original_post]} = result
372+
assert original_post.title == "trigger_nested"
373+
374+
# Verify all posts that should exist after the nested operation
375+
all_posts =
376+
Post
377+
|> Ash.Query.sort(:title)
378+
|> Ash.read!()
379+
380+
# Should have: 1 original + 2 nested = 3 total posts
381+
assert length(all_posts) == 3
382+
383+
# Verify we have the expected posts with correct titles
384+
post_titles = Enum.map(all_posts, & &1.title) |> Enum.sort()
385+
assert post_titles == ["nested_post_1", "nested_post_2", "trigger_nested"]
386+
387+
# Verify the specific nested posts were created by the after_action callback
388+
nested_posts =
389+
Post
390+
|> Ash.Query.filter(expr(title in ["nested_post_1", "nested_post_2"]))
391+
|> Ash.Query.sort(:title)
392+
|> Ash.read!()
393+
394+
assert length(nested_posts) == 2
395+
assert [%{title: "nested_post_1"}, %{title: "nested_post_2"}] = nested_posts
396+
397+
# Verify that each nested post has proper metadata
398+
Enum.each(nested_posts, fn post ->
399+
assert is_binary(post.id)
400+
assert post.title in ["nested_post_1", "nested_post_2"]
401+
end)
402+
end
403+
404+
test "supports bulk_update in after_action callbacks" do
405+
result =
406+
Ash.bulk_create!(
407+
[%{title: "trigger_nested_update"}],
408+
Post,
409+
:create_with_nested_bulk_update,
410+
return_records?: true,
411+
authorize?: false
412+
)
413+
414+
# Assert the bulk result contains the expected data
415+
assert %Ash.BulkResult{records: [original_post]} = result
416+
assert original_post.title == "trigger_nested_update"
417+
418+
# Verify all posts that should exist after the nested operations
419+
all_posts =
420+
Post
421+
|> Ash.Query.sort(:title)
422+
|> Ash.read!()
423+
424+
# Should have: 1 original + 2 created and updated = 3 total posts
425+
assert length(all_posts) == 3
426+
427+
# Verify the original post still exists
428+
original_posts =
429+
Post
430+
|> Ash.Query.filter(expr(title == "trigger_nested_update"))
431+
|> Ash.read!()
432+
433+
assert length(original_posts) == 1
434+
assert hd(original_posts).title == "trigger_nested_update"
435+
436+
# Verify the nested posts were created and then updated by the after_action callback
437+
updated_posts =
438+
Post
439+
|> Ash.Query.filter(expr(title == "updated_via_nested_bulk"))
440+
|> Ash.read!()
441+
442+
assert length(updated_posts) == 2
443+
444+
# Verify that the updated posts have proper metadata and were actually updated
445+
Enum.each(updated_posts, fn post ->
446+
assert is_binary(post.id)
447+
assert post.title == "updated_via_nested_bulk"
448+
end)
449+
450+
# Verify no posts remain with the intermediate titles
451+
intermediate_posts =
452+
Post
453+
|> Ash.Query.filter(expr(title in ["post_to_update_1", "post_to_update_2"]))
454+
|> Ash.read!()
455+
456+
assert length(intermediate_posts) == 0,
457+
"Posts should have been updated, not left with intermediate titles"
458+
end
459+
460+
test "nested bulk operations handle metadata indexing correctly" do
461+
# Create multiple posts in the parent bulk operation to test indexing
462+
result =
463+
Ash.bulk_create!(
464+
[
465+
%{title: "trigger_nested"},
466+
%{title: "trigger_nested_2"}
467+
],
468+
Post,
469+
:create_with_nested_bulk_create,
470+
return_records?: true,
471+
authorize?: false
472+
)
473+
474+
# Assert both parent posts were created
475+
assert %Ash.BulkResult{records: parent_posts} = result
476+
assert length(parent_posts) == 2
477+
478+
parent_titles = Enum.map(parent_posts, & &1.title) |> Enum.sort()
479+
assert parent_titles == ["trigger_nested", "trigger_nested_2"]
480+
481+
# Verify total posts: 2 parent + (2 nested per parent) = 6 total
482+
all_posts = Post |> Ash.Query.sort(:title) |> Ash.read!()
483+
assert length(all_posts) == 6
484+
485+
# Count posts by type
486+
nested_posts =
487+
Post
488+
|> Ash.Query.filter(expr(title in ["nested_post_1", "nested_post_2"]))
489+
|> Ash.read!()
490+
491+
# Should have 4 nested posts (2 for each parent operation)
492+
assert length(nested_posts) == 4
493+
494+
# Verify each nested post has proper structure
495+
Enum.each(nested_posts, fn post ->
496+
assert is_binary(post.id)
497+
assert post.title in ["nested_post_1", "nested_post_2"]
498+
end)
499+
end
500+
end
358501
end

test/support/resources/post.ex

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,56 @@ defmodule AshPostgres.Test.Post do
432432
upsert_fields([:price])
433433
end
434434

435+
create :create_with_nested_bulk_create do
436+
after_action(fn changeset, result, context ->
437+
# Perform a nested bulk_create within an after_action callback
438+
# This tests the context key collision scenario
439+
Ash.bulk_create!(
440+
[%{title: "nested_post_1"}, %{title: "nested_post_2"}],
441+
__MODULE__,
442+
:create,
443+
authorize?: false,
444+
tenant: changeset.tenant
445+
)
446+
447+
{:ok, result}
448+
end)
449+
end
450+
451+
create :create_with_nested_bulk_update do
452+
after_action(fn changeset, result, context ->
453+
# First create some posts to update
454+
created_posts =
455+
Ash.bulk_create!(
456+
[%{title: "post_to_update_1"}, %{title: "post_to_update_2"}],
457+
__MODULE__,
458+
:create,
459+
authorize?: false,
460+
tenant: changeset.tenant,
461+
return_records?: true
462+
)
463+
464+
# Then perform a nested bulk_update within an after_action callback
465+
# This tests the context key collision scenario for bulk_update
466+
post_ids = Enum.map(created_posts.records, & &1.id)
467+
468+
Ash.bulk_update!(
469+
__MODULE__,
470+
:set_title,
471+
%{title: "updated_via_nested_bulk"},
472+
filter: [id: [in: post_ids]],
473+
authorize?: false,
474+
tenant: changeset.tenant
475+
)
476+
477+
{:ok, result}
478+
end)
479+
end
480+
481+
update :set_title do
482+
accept([:title])
483+
end
484+
435485
update :set_title_from_author do
436486
change(atomic_update(:title, expr(author.first_name)))
437487
end

0 commit comments

Comments
 (0)