defmodule AshPostgres.AtomicsTest do alias AshPostgres.Test.Author use AshPostgres.RepoCase, async: false alias AshPostgres.Test.Post import Ash.Expr require Ash.Query test "atomics work on upserts" do id = Ash.UUID.generate() Post |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) |> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{id: id, title: "foo", price: 1}, upsert?: true) |> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Ash.create!() assert [%{price: 2}] = Post |> Ash.read!() end test "a basic atomic works" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.create!() assert %{price: 2} = post |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.atomic_update(:price, expr(price + 1)) |> Ash.update!() end test "an atomic validation is based on where it appears in the action" do post = Post |> Ash.Changeset.for_create(:create, %{title: "bar"}) |> Ash.create!() # just asserting that there is no exception here post |> Ash.Changeset.for_update(:change_title_to_foo_unless_its_already_foo) |> Ash.update!() end test "an atomic works with a datetime" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.create!() now = DateTime.utc_now() assert %{created_at: ^now} = post |> Ash.Changeset.new() |> Ash.Changeset.atomic_update(:created_at, expr(^now)) |> Ash.Changeset.for_update(:update, %{}) |> Ash.update!() end test "an atomic that violates a constraint will return the proper error" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.create!() assert_raise Ash.Error.Invalid, ~r/does not exist/, fn -> post |> Ash.Changeset.new() |> Ash.Changeset.atomic_update(:organization_id, Ash.UUID.generate()) |> Ash.Changeset.for_update(:update, %{}) |> Ash.update!() end end test "an atomic can refer to a calculation" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.create!() post = post |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.atomic_update(:score, expr(score_after_winning)) |> Ash.update!() assert post.score == 1 end test "an atomic can be attached to an action" do post = Post |> Ash.Changeset.for_create(:create, %{title: "foo", price: 1}) |> Ash.create!() assert Post.increment_score!(post, 2).score == 2 assert Post.increment_score!(post, 2).score == 4 end test "relationships can be used in atomic update" do author = Author |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) |> Ash.create!() post = Post |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) |> Ash.create!() post = post |> Ash.Changeset.for_update(:set_title_from_author, %{}) |> Ash.update!() assert post.title == "John" end test "relationships can be used in atomic update and in an atomic update filter" do author = Author |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) |> Ash.create!() post = Post |> Ash.Query.filter(author.last_name == "Doe") |> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true) |> Map.get(:records) |> List.first() assert post.title == "John" end test "relationships can be used in atomic update and in an atomic update filter when first join is a left join" do author = Author |> Ash.Changeset.for_create(:create, %{first_name: "John", last_name: "Doe"}) |> Ash.create!() Post |> Ash.Changeset.for_create(:create, %{price: 1, author_id: author.id}) |> Ash.create!() assert [] = Post |> Ash.Query.filter(is_nil(author.last_name)) |> Ash.bulk_update!(:set_title_from_author, %{}, return_records?: true) |> Map.get(:records) end end