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