Skip to content

Commit 62931aa

Browse files
committed
fragments in atomic_set
1 parent d500777 commit 62931aa

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

lib/data_layer.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,9 @@ defmodule AshPostgres.DataLayer do
706706
def can?(resource, {:atomic, :upsert}),
707707
do: not AshPostgres.DataLayer.Info.repo(resource, :mutate).disable_atomic_actions?()
708708

709+
def can?(resource, {:atomic, :create}),
710+
do: not AshPostgres.DataLayer.Info.repo(resource, :mutate).disable_atomic_actions?()
711+
709712
def can?(_, :upsert), do: true
710713
def can?(_, :changeset_filter), do: true
711714

@@ -2103,6 +2106,15 @@ defmodule AshPostgres.DataLayer do
21032106

21042107
atomic_insert_values =
21052108
if create_atomics != [] do
2109+
# Hydrate expressions to convert Ash.Query.Call structs to proper function structs
2110+
create_atomics =
2111+
Enum.map(create_atomics, fn {key, expr} ->
2112+
case Ash.Filter.hydrate_refs(expr, %{resource: resource, public?: false}) do
2113+
{:ok, hydrated_expr} -> {key, hydrated_expr}
2114+
{:error, error} -> raise Ash.Error.to_ash_error(error)
2115+
end
2116+
end)
2117+
21062118
query = from(row in source, as: ^0)
21072119

21082120
query =

test/atomics_test.exs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,89 @@ defmodule AshPostgres.AtomicsTest do
419419
end
420420
end
421421
)
422+
423+
describe "atomic create (create_atomics)" do
424+
# Tests for atomic_set on create actions - supported in Ash 3.14+
425+
426+
test "atomic_set works on create with fragment subquery" do
427+
# Create 3 initial posts
428+
Enum.each(1..3, fn i ->
429+
Post
430+
|> Ash.Changeset.for_create(:create, %{title: "post_#{i}", price: i})
431+
|> Ash.create!()
432+
end)
433+
434+
# Use atomic_set to set score to count of existing posts
435+
post =
436+
Post
437+
|> Ash.Changeset.for_create(:create, %{title: "new_post", price: 10})
438+
|> Ash.Changeset.atomic_set(
439+
:score,
440+
expr(fragment("(SELECT count(*) FROM posts WHERE type = 'sponsored')"))
441+
)
442+
|> Ash.create!()
443+
444+
# Score should be 3 (count of existing sponsored posts when INSERT ran)
445+
assert post.score == 3
446+
end
447+
448+
test "atomic_set works on create with simple literal expression" do
449+
post =
450+
Post
451+
|> Ash.Changeset.for_create(:create, %{title: "test", price: 5})
452+
|> Ash.Changeset.atomic_set(:score, expr(42))
453+
|> Ash.create!()
454+
455+
assert post.score == 42
456+
end
457+
458+
test "atomic_set works on create with arithmetic expression" do
459+
post =
460+
Post
461+
|> Ash.Changeset.for_create(:create, %{title: "test", price: 10})
462+
|> Ash.Changeset.atomic_set(:score, expr(5 + 15))
463+
|> Ash.create!()
464+
465+
assert post.score == 20
466+
end
467+
468+
test "atomic_set on create overrides attributes when both are set" do
469+
# If both attributes and atomic_set set a value, atomics should win
470+
post =
471+
Post
472+
|> Ash.Changeset.for_create(:create, %{title: "test", price: 5, score: 999})
473+
|> Ash.Changeset.atomic_set(:score, expr(100))
474+
|> Ash.create!()
475+
476+
# The atomic expression should override the attribute value
477+
assert post.score == 100
478+
end
479+
480+
test "atomic_set on create works sequentially" do
481+
# Create 2 initial posts
482+
Enum.each(1..2, fn i ->
483+
Post
484+
|> Ash.Changeset.for_create(:create, %{title: "initial_#{i}", price: i})
485+
|> Ash.create!()
486+
end)
487+
488+
# Create posts one by one with atomic_set
489+
results =
490+
Enum.map(1..3, fn i ->
491+
Post
492+
|> Ash.Changeset.for_create(:create, %{title: "new_#{i}", price: i})
493+
|> Ash.Changeset.atomic_set(
494+
:score,
495+
expr(fragment("(SELECT count(*) FROM posts WHERE type = 'sponsored')"))
496+
)
497+
|> Ash.create!()
498+
end)
499+
500+
# First post sees 2 existing posts
501+
assert Enum.at(results, 0).score == 2
502+
# Subsequent posts see incrementing counts
503+
assert Enum.at(results, 1).score == 3
504+
assert Enum.at(results, 2).score == 4
505+
end
506+
end
422507
end

0 commit comments

Comments
 (0)