2020-08-25 16:49:07 +12:00
|
|
|
defmodule Ash.Test.CalculationTest do
|
|
|
|
@moduledoc false
|
|
|
|
use ExUnit.Case, async: true
|
|
|
|
|
|
|
|
defmodule Concat do
|
|
|
|
# An example concatenation calculation, that accepts the delimeter as an argument
|
2021-06-04 17:37:11 +12:00
|
|
|
use Ash.Calculation
|
2020-08-25 16:49:07 +12:00
|
|
|
|
|
|
|
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, %{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 User do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Resource, data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
2021-03-08 18:59:32 +13:00
|
|
|
read :read
|
|
|
|
create :create
|
|
|
|
update :update
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2021-01-13 09:40:55 +13:00
|
|
|
uuid_primary_key :id
|
2020-08-25 16:49:07 +12:00
|
|
|
attribute :first_name, :string
|
|
|
|
attribute :last_name, :string
|
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
2020-12-01 17:18:39 +13:00
|
|
|
calculate :full_name, :string, {Concat, keys: [:first_name, :last_name]} do
|
2021-05-21 08:42:26 +12:00
|
|
|
select [:first_name, :last_name]
|
2021-01-16 14:41:21 +13:00
|
|
|
# We currently need to use the [allow_empty?: true, trim?: false] constraints here.
|
|
|
|
# As it's an empty string, the separator would otherwise be trimmed and set to `nil`.
|
|
|
|
argument :separator, :string,
|
|
|
|
default: " ",
|
|
|
|
constraints: [allow_empty?: true, trim?: false]
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
2021-06-04 17:37:11 +12:00
|
|
|
|
|
|
|
calculate :expr_full_name, :string, expr(first_name <> " " <> last_name)
|
|
|
|
|
|
|
|
calculate :conditional_full_name,
|
|
|
|
:string,
|
|
|
|
expr(if(first_name and last_name, first_name <> " " <> last_name, "(none)"))
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-07 19:41:02 +13:00
|
|
|
defmodule Registry do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Registry
|
|
|
|
|
|
|
|
entries do
|
|
|
|
entry(User)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
defmodule Api do
|
|
|
|
@moduledoc false
|
|
|
|
use Ash.Api
|
|
|
|
|
|
|
|
resources do
|
2021-10-07 19:41:02 +13:00
|
|
|
registry Registry
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
setup do
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.new(%{first_name: "zach", last_name: "daniel"})
|
|
|
|
|> Api.create!()
|
|
|
|
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.new(%{first_name: "brian", last_name: "cranston"})
|
|
|
|
|> Api.create!()
|
|
|
|
|
|
|
|
:ok
|
|
|
|
end
|
|
|
|
|
|
|
|
test "it uses default arguments" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:full_name)
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian cranston", "zach daniel"]
|
|
|
|
end
|
|
|
|
|
|
|
|
test "arguments can be supplied" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(full_name: %{separator: " - "})
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian - cranston", "zach - daniel"]
|
|
|
|
end
|
|
|
|
|
2021-05-21 08:42:26 +12:00
|
|
|
test "fields are selected if necessary for the calculation" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.select(:first_name)
|
|
|
|
|> Ash.Query.load(full_name: %{separator: " - "})
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian - cranston", "zach - daniel"]
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
test "custom calculations can be added to a query" do
|
|
|
|
full_names =
|
|
|
|
User
|
2021-06-04 17:37:11 +12:00
|
|
|
|> Ash.Query.calculate(:full_name, {Concat, keys: [:first_name, :last_name]}, :string, %{
|
2020-08-25 16:49:07 +12:00
|
|
|
separator: " \o.o/ "
|
|
|
|
})
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.calculations.full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
2020-08-25 18:06:01 +12:00
|
|
|
assert full_names == ["brian \o.o/ cranston", "zach \o.o/ daniel"]
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
2021-06-04 17:37:11 +12:00
|
|
|
|
|
|
|
test "expression based calculations are resolved via evaluating the expression" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:expr_full_name)
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.expr_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian cranston", "zach daniel"]
|
|
|
|
end
|
|
|
|
|
2021-06-06 10:11:09 +12:00
|
|
|
test "expression based calculations can be sorted on" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:expr_full_name)
|
|
|
|
|> Ash.Query.sort(:expr_full_name)
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.expr_full_name)
|
|
|
|
|
|
|
|
assert full_names == ["brian cranston", "zach daniel"]
|
|
|
|
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:expr_full_name)
|
|
|
|
|> Ash.Query.sort(expr_full_name: :desc)
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.expr_full_name)
|
|
|
|
|
|
|
|
assert full_names == ["zach daniel", "brian cranston"]
|
|
|
|
end
|
|
|
|
|
2021-06-04 17:37:11 +12:00
|
|
|
test "the `if` calculation resolves the first expr when true, and the second when false" do
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.new(%{first_name: "bob"})
|
|
|
|
|> Api.create!()
|
|
|
|
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:conditional_full_name)
|
|
|
|
|> Api.read!()
|
|
|
|
|> Enum.map(& &1.conditional_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["(none)", "brian cranston", "zach daniel"]
|
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|