defmodule Ash.Test.Resource.ResourceTest do @moduledoc false use ExUnit.Case, async: true alias Ash.Test.Domain, as: Domain defmacrop defposts(do: body) do module = Module.concat(["rand#{System.unique_integer([:positive])}", Post]) quote do defmodule unquote(module) do @moduledoc false use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets actions do default_accept :* defaults [:read, :destroy, create: :*, update: :*] end attributes do uuid_primary_key :id end unquote(body) end alias unquote(module), as: Post end end defmodule Concat do # An example concatenation calculation, that accepts the delimiter as an argument use Ash.Resource.Calculation def init(opts) do if opts[:keys] && is_list(opts[:keys]) && Enum.all?(opts[:keys], &is_atom/1) do {:ok, opts} else {:error, "Expected a `keys` option for which keys to concat"} end end def calculate(records, opts, %Ash.Resource.Calculation.Context{ arguments: %{separator: separator} }) do Enum.map(records, fn record -> Enum.map_join(opts[:keys], separator, fn key -> to_string(Map.get(record, key)) end) end) end end defmodule Post do @moduledoc false use Ash.Resource, domain: Domain actions do default_accept :* defaults [:read, :destroy, create: :*, update: :*] end attributes do uuid_primary_key :id attribute :name, :string do public?(true) end end end test "it returns the correct error when doing a read with no data layer setup" do Post |> Ash.Changeset.for_create(:create, %{name: "foo"}) |> Ash.create() {_, error} = Ash.read(Post) [%Ash.Error.SimpleDataLayer.NoDataProvided{message: message} | _] = error.errors assert message != nil end test "fails if there are multiple fields that share the same name" do assert_raise( Spark.Error.DslError, ~r/There are 4 fields\(attributes, calculations, aggregates, and relationships\) that share the name `foobar`/, fn -> defposts do attributes do attribute(:foobar, :string, public?: true) end relationships do belongs_to :foobar, Foobar do public?(true) end end calculations do calculate :foobar, :integer, {Concat, keys: [:foo, :bar]} do public?(true) end end aggregates do count :foobar, :baz do public? true end end end end ) end test "relationships can be loaded" do defmodule Leg do @moduledoc false use Ash.Resource, domain: nil, data_layer: Ash.DataLayer.Ets, validate_domain_inclusion?: false attributes do uuid_primary_key :id attribute :side, :string, allow_nil?: false, public?: true end relationships do belongs_to :pants, Ash.Test.Resource.ResourceTest.Pants do attribute_writable? true public? true end end actions do defaults [:create, :read] default_accept :* end end defmodule Pants do @moduledoc false use Ash.Resource, domain: nil, data_layer: Ash.DataLayer.Ets, validate_domain_inclusion?: false attributes do uuid_primary_key :id end relationships do has_many :legs, Leg end actions do defaults [:create, :read] default_accept :* end end defmodule Clothing do @moduledoc false use Ash.Domain, validate_config_inclusion?: false resources do allow_unregistered? true end end pants = Pants |> Ash.Changeset.for_create(:create, %{}, domain: Clothing) |> Ash.create!(domain: Clothing) [left, _right] = ~w[left right] |> Enum.map(fn side -> Leg |> Ash.Changeset.for_create(:create, %{pants_id: pants.id, side: side}, domain: Clothing) |> Ash.create!(domain: Clothing) end) Ash.load!(left, [pants: [:legs]], domain: Clothing) end test "no attributes relationships can be loaded" do defmodule TimeMachine do @moduledoc false use Ash.Resource, domain: Ash.Test.Domain, data_layer: Ash.DataLayer.Ets, validate_domain_inclusion?: false attributes do uuid_primary_key :id attribute :name, :string, public?: true attribute :current_year, :integer, public?: true end relationships do has_one :next_machine, __MODULE__ do no_attributes? true filter expr(current_year > parent(current_year) and id != parent(id)) sort current_year: :asc public? true writable? false from_many? true end end actions do defaults [:create, :read] default_accept :* end end delorean = Ash.create!(TimeMachine, %{name: "Delorean DMC-12", current_year: 1985}) Ash.create!(TimeMachine, %{name: "San Dimas Phone Booth", current_year: 1988}) delorean = Ash.load!(delorean, [:next_machine]) assert delorean.next_machine.name == "San Dimas Phone Booth" delorean = Ash.load!(delorean, next_machine: [:name]) assert delorean.next_machine.name == "San Dimas Phone Booth" end end