defmodule Ash.Test.ReactorCreateTest do @moduledoc false use ExUnit.Case, async: true alias Ash.Test.Domain defmodule Author do @moduledoc false use Ash.Resource, data_layer: Ash.DataLayer.Ets, domain: Domain ets do private? true end multitenancy do strategy :attribute attribute :organisation global? true end attributes do uuid_primary_key :id attribute :name, :string, allow_nil?: false, public?: true attribute :organisation, :string, allow_nil?: true, public?: true end actions do default_accept :* defaults [:read, :destroy, create: :*, update: :*] destroy :undo_create do argument :changeset, :struct do constraints instance_of: Ash.Changeset end end end relationships do has_many :posts, Ash.Test.ReactorCreateTest.Post do public? true end end end defmodule Post do @moduledoc false use Ash.Resource, data_layer: Ash.DataLayer.Ets, domain: Domain ets do private? true end attributes do uuid_primary_key :id attribute :title, :string, allow_nil?: false, public?: true attribute :sub_title, :string, public?: true end actions do default_accept :* defaults [:read, :destroy, create: :*, update: :*] create :with_actor_as_author do change relate_actor(:author) end end relationships do belongs_to :author, Ash.Test.ReactorCreateTest.Author do public? true allow_nil? true end end end test "it can create a post" do defmodule SimpleCreatePostReactor do @moduledoc false use Reactor, extensions: [Ash.Reactor] ash do default_domain(Domain) end input :title input :sub_title create :create_post, Post, :create do inputs(%{title: input(:title), sub_title: input(:sub_title)}) end end assert {:ok, post} = Reactor.run(SimpleCreatePostReactor, %{title: "Title", sub_title: "Sub-title"}) assert post.title == "Title" assert post.sub_title == "Sub-title" assert post.__meta__.state == :loaded end test "it defaults to the primary action when the action is not supplied" do defmodule InferredActionNameCreatePostReactor do @moduledoc false use Reactor, extensions: [Ash.Reactor] ash do default_domain(Domain) end input :title input :sub_title create :create_post, Post do inputs(%{title: input(:title), sub_title: input(:sub_title)}) end end assert {:ok, _post} = Reactor.run(InferredActionNameCreatePostReactor, %{ title: "Title", sub_title: "Sub-title" }) end test "it merges multiple `inputs` entities together" do defmodule MergedInputsCreatePostReactor do @moduledoc false use Reactor, extensions: [Ash.Reactor] ash do default_domain(Domain) end input :title input :sub_title create :create_post, Post, :create do inputs(%{title: input(:title)}) inputs(%{sub_title: input(:sub_title)}) end end assert {:ok, post} = Reactor.run(MergedInputsCreatePostReactor, %{ title: "Title", sub_title: "Sub-title" }) assert post.title == "Title" assert post.sub_title == "Sub-title" end test "`inputs` entities can be transformed separately" do defmodule TransformedInputsCreatePostReactor do @moduledoc false use Reactor, extensions: [Ash.Reactor] ash do default_domain(Domain) end input :title create :create_post, Post, :create do inputs %{title: input(:title)} do transform &%{title: String.upcase(&1.title)} end inputs %{sub_title: input(:title)} do transform &%{sub_title: String.downcase(&1.sub_title)} end end end assert {:ok, post} = Reactor.run(TransformedInputsCreatePostReactor, %{title: "Title"}) assert post.title == "TITLE" assert post.sub_title == "title" end test "it can provide an actor" do defmodule CreateWithActorCreatePostReactor do @moduledoc false use Reactor, extensions: [Ash.Reactor] ash do default_domain(Domain) end input :author_name input :title input :sub_title create :create_author, Author, :create do inputs(%{name: input(:author_name)}) end create :create_post, Post, :with_actor_as_author do inputs(%{title: input(:title), sub_title: input(:sub_title)}) actor(result(:create_author)) end end assert {:ok, post} = Reactor.run(CreateWithActorCreatePostReactor, %{ author_name: "Marty McFly", title: "Title", sub_title: "Sub-title" }) assert post.author.name == "Marty McFly" assert post.title == "Title" end test "it can provide a tenant" do defmodule TenantedCreateAuthorReactor do @moduledoc false use Reactor, extensions: [Ash.Reactor] ash do default_domain(Domain) end input :author_name input :organisation_name create :create_author, Author, :create do inputs(%{name: input(:author_name)}) tenant(input(:organisation_name)) end end assert {:ok, author} = Reactor.run(TenantedCreateAuthorReactor, %{ author_name: "Marty McFly", organisation_name: "Hill Valley High School" }) assert author.name == "Marty McFly" assert author.organisation == "Hill Valley High School" end test "it can undo the creation on error" do defmodule UndoingCreateAuthorReactor do @moduledoc false use Ash.Reactor ash do default_domain(Domain) end input :author_name create :create_author, Author, :create do inputs(%{name: input(:author_name)}) undo :always undo_action(:undo_create) end step :fail do argument :author, result(:create_author) run fn _, _ -> assert [_] = Ash.read!(Author) raise "hell" end end end UndoingCreateAuthorReactor |> Reactor.run(%{author_name: "Marty McFly"}, %{}, async?: false) |> Ash.Test.assert_has_error(fn %Reactor.Error.Invalid.RunStepError{error: %RuntimeError{message: "hell"}} -> true _ -> false end) assert [] = Ash.read!(Author) end test "it can be provided a changeset as the initial value" do defmodule CreateFromChangesetReactor do @moduledoc false use Ash.Reactor step :create_changeset do run fn _ -> changeset = Post |> Ash.Changeset.for_create(:create, %{title: "Foo", sub_title: "Bar"}) {:ok, changeset} end end create :create_post, Post do initial(result(:create_changeset)) end return :create_post end assert {:ok, post} = Reactor.run(CreateFromChangesetReactor, %{}, %{}, async?: false) assert post.title == "Foo" assert post.sub_title == "Bar" end end