2020-08-25 16:49:07 +12:00
|
|
|
defmodule Ash.Test.CalculationTest do
|
|
|
|
@moduledoc false
|
|
|
|
use ExUnit.Case, async: true
|
|
|
|
|
2023-02-22 13:05:40 +13:00
|
|
|
require Ash.Query
|
2024-03-28 09:06:40 +13:00
|
|
|
alias Ash.Test.Domain, as: Domain
|
2023-02-22 13:05:40 +13:00
|
|
|
|
2023-03-29 13:31:47 +13:00
|
|
|
defmodule FriendLink do
|
|
|
|
use Ash.Resource,
|
2024-03-28 09:06:40 +13:00
|
|
|
domain: Domain,
|
2023-03-29 13:31:47 +13:00
|
|
|
data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
ets do
|
2023-07-13 16:08:51 +12:00
|
|
|
private?(true)
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults([:read, :destroy, create: :*, update: :*])
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
2023-04-26 15:31:01 +12:00
|
|
|
belongs_to :source, Ash.Test.CalculationTest.User do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
allow_nil?(false)
|
|
|
|
primary_key?(true)
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
2023-04-26 15:31:01 +12:00
|
|
|
belongs_to :target, Ash.Test.CalculationTest.User do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
allow_nil?(false)
|
|
|
|
primary_key?(true)
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-30 14:46:30 +13:00
|
|
|
defmodule FullNameWithSelect do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-03-30 14:46:30 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
def load(_, _, _) do
|
2023-03-30 14:46:30 +13:00
|
|
|
[:first_name, :last_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
"#{record.first_name} #{record.last_name}"
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-02-02 04:21:22 +13:00
|
|
|
defmodule Metadata do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2024-02-02 04:21:22 +13:00
|
|
|
|
|
|
|
def calculate(records, _opts, _context) do
|
|
|
|
Enum.map(records, &Ash.Resource.get_metadata(&1, :example_metadata))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
defmodule Concat do
|
2022-10-05 09:01:42 +13:00
|
|
|
# An example concatenation calculation, that accepts the delimiter as an argument
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.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
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
def calculate(records, opts, %{arguments: %{separator: separator}}) do
|
2020-08-25 16:49:07 +12:00
|
|
|
Enum.map(records, fn record ->
|
|
|
|
Enum.map_join(opts[:keys], separator, fn key ->
|
|
|
|
to_string(Map.get(record, key))
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
end
|
2024-03-28 09:06:40 +13:00
|
|
|
|
|
|
|
def expression(opts, context) do
|
|
|
|
expr(string_join(^Enum.map(opts[:keys], &ref/1), ^context.arguments.separator))
|
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
|
2023-02-24 03:45:27 +13:00
|
|
|
defmodule NameWithUsersName do
|
|
|
|
# Don't do this kind of thing in real life
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-02-24 03:45:27 +13:00
|
|
|
|
|
|
|
def load(_, _, _) do
|
|
|
|
[:full_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _opts, %{actor: actor}) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.full_name <> " " <> actor.first_name <> " " <> actor.last_name
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-27 14:57:07 +12:00
|
|
|
defmodule ConcatWithLoad do
|
2022-10-05 09:01:42 +13:00
|
|
|
# An example concatenation calculation, that accepts the delimiter as an argument
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2022-04-27 14:57: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
|
|
|
|
|
2022-04-29 10:07:06 +12:00
|
|
|
def load(_query, opts, _) do
|
2022-04-27 14:57:07 +12:00
|
|
|
opts[:keys]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, opts, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
Enum.map_join(opts[:keys], " ", fn key ->
|
|
|
|
to_string(Map.get(record, key))
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-29 10:17:59 +12:00
|
|
|
defmodule BestFriendsName do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2022-04-29 10:17:59 +12:00
|
|
|
|
2022-05-04 10:56:37 +12:00
|
|
|
def load(_query, _opts, _) do
|
2022-04-29 10:17:59 +12:00
|
|
|
[best_friend: :full_name]
|
|
|
|
end
|
|
|
|
|
2022-04-29 12:12:54 +12:00
|
|
|
def calculate(records, _opts, _) do
|
2022-04-29 10:17:59 +12:00
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.best_friend && record.best_friend.full_name
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
defmodule BestFriendsFirstNameThatFails do
|
|
|
|
use Ash.Resource.Calculation
|
|
|
|
|
|
|
|
def load(_query, _opts, _) do
|
|
|
|
[:best_friend]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _opts, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.best_friend && record.best_friend.first_name
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-22 13:05:40 +13:00
|
|
|
defmodule NamesOfBestFriendsOfMe do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-02-22 13:05:40 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
def load(_query, _opts, %{arguments: args}) do
|
2023-02-22 13:05:40 +13:00
|
|
|
if args[:only_special] do
|
|
|
|
query =
|
2023-03-29 13:31:47 +13:00
|
|
|
Ash.Test.CalculationTest.User
|
2023-02-22 13:05:40 +13:00
|
|
|
|> Ash.Query.filter(special == true)
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Ash.Query.load(:full_name)
|
2023-02-22 13:05:40 +13:00
|
|
|
|
|
|
|
[best_friends_of_me: query]
|
|
|
|
else
|
|
|
|
[best_friends_of_me: :full_name]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _opts, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.best_friends_of_me
|
|
|
|
|> Enum.map(& &1.full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|> Enum.join(" - ")
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-31 08:40:36 +13:00
|
|
|
defmodule BestFriendsFirstNamePlusStuff do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2022-12-31 08:40:36 +13:00
|
|
|
|
|
|
|
def load(_query, _opts, _) do
|
|
|
|
[:best_friends_first_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _opts, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.best_friends_first_name && record.best_friends_first_name <> " stuff"
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-30 01:00:42 +13:00
|
|
|
defmodule BestFriendsBestFriendsFirstName do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-03-30 01:00:42 +13:00
|
|
|
|
|
|
|
def load(_query, _opts, _) do
|
|
|
|
[best_friend: :best_friends_first_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _opts, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
"bf: #{record.best_friend && record.best_friend.best_friends_first_name}"
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-30 14:34:50 +13:00
|
|
|
defmodule FullNamePlusFirstName do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-03-30 14:34:50 +13:00
|
|
|
|
|
|
|
def load(_, _, _) do
|
2024-03-28 09:06:40 +13:00
|
|
|
[:first_name, :full_name_with_select]
|
2023-03-30 14:34:50 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _, _) do
|
|
|
|
Enum.map(records, fn record ->
|
2023-03-30 14:46:30 +13:00
|
|
|
record.full_name_with_select <> " #{record.first_name}"
|
2023-03-30 14:34:50 +13:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-29 13:31:47 +13:00
|
|
|
defmodule FriendsNames do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-03-29 13:31:47 +13:00
|
|
|
|
|
|
|
def load(_query, _opts, _) do
|
|
|
|
[
|
|
|
|
friends:
|
|
|
|
Ash.Test.CalculationTest.User
|
|
|
|
|> Ash.Query.load([:full_name, :prefix])
|
|
|
|
|> Ash.Query.limit(5)
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _opts, _) do
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.friends
|
|
|
|
|> Enum.map_join(" | ", fn friend ->
|
|
|
|
if friend.prefix do
|
|
|
|
friend.prefix <> " " <> friend.full_name
|
|
|
|
else
|
|
|
|
friend.full_name
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-25 01:23:38 +13:00
|
|
|
defmodule RoleHasUser do
|
|
|
|
@moduledoc "never do this, this is done for testing only"
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-11-25 01:23:38 +13:00
|
|
|
|
|
|
|
def load(_, _, _) do
|
|
|
|
[:user]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(list, _opts, _context) do
|
|
|
|
Enum.map(list, &(not is_nil(&1.user)))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defmodule RolesHaveUser do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-11-25 01:23:38 +13:00
|
|
|
|
|
|
|
def load(_, _, _) do
|
|
|
|
[roles: [:has_user]]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(list, _, _) do
|
|
|
|
Enum.map(list, fn user ->
|
|
|
|
Enum.all?(user.roles, & &1.has_user)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-31 01:27:37 +13:00
|
|
|
defmodule Role do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2023-03-31 01:27:37 +13:00
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults([:read, :destroy, create: :*, update: :*])
|
2024-02-26 02:24:19 +13:00
|
|
|
|
|
|
|
read :by_user_name do
|
|
|
|
argument :user_name, :string, allow_nil?: false
|
|
|
|
filter expr(user_name == ^arg(:user_name))
|
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
ets do
|
2023-07-13 16:08:51 +12:00
|
|
|
private?(true)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
aggregates do
|
|
|
|
first :user_is_active, :user, :is_active do
|
2024-03-28 09:06:40 +13:00
|
|
|
public? true
|
2023-07-13 16:08:51 +12:00
|
|
|
default(false)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
2023-11-14 20:24:19 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
list :names, :user, :first_name do
|
|
|
|
public? true
|
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2023-07-13 16:08:51 +12:00
|
|
|
uuid_primary_key(:id)
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute(:name, :string, public?: true)
|
2023-03-31 01:27:37 +13:00
|
|
|
|
|
|
|
attribute :is_active, :boolean do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
allow_nil?(false)
|
|
|
|
default(false)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
2023-06-24 23:55:38 +12:00
|
|
|
calculate :active, :boolean do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
calculation(expr(is_active && user_is_active))
|
|
|
|
load([:user_is_active])
|
2023-06-24 23:55:38 +12:00
|
|
|
end
|
2023-10-10 11:50:15 +13:00
|
|
|
|
2024-01-19 18:05:42 +13:00
|
|
|
calculate :active_elixir, :boolean do
|
|
|
|
calculation fn record, _ ->
|
|
|
|
record.is_active && record.user_is_active
|
|
|
|
end
|
|
|
|
|
|
|
|
load [:is_active, :user_is_active]
|
|
|
|
end
|
|
|
|
|
2023-11-25 01:23:38 +13:00
|
|
|
calculate :has_user, :boolean, RoleHasUser
|
|
|
|
|
2023-10-10 11:50:15 +13:00
|
|
|
calculate :user_is_active_with_calc, :boolean, expr(user.is_active || false)
|
2023-11-14 20:24:19 +13:00
|
|
|
|
|
|
|
calculate :user_name,
|
|
|
|
:string,
|
|
|
|
expr(
|
|
|
|
if is_nil(^actor(:id)) do
|
|
|
|
"no actor"
|
|
|
|
else
|
|
|
|
at(names, 0)
|
|
|
|
end
|
|
|
|
)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
2024-02-26 02:24:19 +13:00
|
|
|
belongs_to(:user, Ash.Test.CalculationTest.User) do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2024-02-26 02:24:19 +13:00
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-01-19 18:05:42 +13:00
|
|
|
defmodule Bio do
|
2024-04-12 14:28:28 +12:00
|
|
|
use Ash.Resource, data_layer: :embedded
|
2024-01-19 18:05:42 +13:00
|
|
|
|
|
|
|
attributes do
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute :greeting, :string do
|
|
|
|
public?(true)
|
|
|
|
end
|
2024-01-19 18:05:42 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
|
|
|
calculate :say_hello, :string, expr(greeting <> " " <> ^arg(:to)) do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2024-01-19 18:05:42 +13:00
|
|
|
argument :to, :string, allow_nil?: false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
defmodule User do
|
|
|
|
@moduledoc false
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets
|
2020-08-25 16:49:07 +12:00
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults([:read, :destroy, create: :*, update: :*])
|
2023-03-01 04:03:35 +13:00
|
|
|
|
|
|
|
read :paginated do
|
|
|
|
pagination do
|
2023-07-13 16:08:51 +12:00
|
|
|
keyset?(true)
|
|
|
|
default_limit(10)
|
2023-03-01 04:03:35 +13:00
|
|
|
end
|
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2023-07-13 16:08:51 +12:00
|
|
|
uuid_primary_key(:id)
|
2024-03-28 09:06:40 +13:00
|
|
|
attribute(:first_name, :string, public?: true)
|
|
|
|
attribute(:last_name, :string, public?: true)
|
|
|
|
attribute(:prefix, :string, public?: true)
|
|
|
|
attribute(:special, :boolean, public?: true)
|
|
|
|
attribute(:is_active, :boolean, public?: true)
|
|
|
|
attribute(:bio, Bio, public?: true)
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
|
2024-02-02 04:21:22 +13:00
|
|
|
changes do
|
|
|
|
change fn changeset, _ ->
|
|
|
|
if changeset.action_type == :create do
|
|
|
|
Ash.Changeset.after_action(changeset, fn changeset, result ->
|
|
|
|
{:ok, Ash.Resource.put_metadata(result, :example_metadata, "example metadata")}
|
|
|
|
end)
|
|
|
|
else
|
|
|
|
changeset
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
calculations do
|
2024-01-19 18:05:42 +13:00
|
|
|
calculate :say_hello_to_fred, :string do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
|
|
|
|
|
|
|
calculation fn records, _ ->
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.bio.say_hello
|
|
|
|
end)
|
2024-01-19 18:05:42 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
load bio: [say_hello: %{to: "Fred"}]
|
|
|
|
end
|
|
|
|
|
2024-03-25 16:25:28 +13:00
|
|
|
calculate :bio_greeting, :string, expr(bio[:greeting])
|
|
|
|
|
|
|
|
calculate :say_hello_to_fred_expr, :string, expr(record.bio.say_hello(to: "Fred"))
|
|
|
|
|
|
|
|
calculate :say_hello_to_george_expr, :string, expr(record.bio.say_hello(to: "George"))
|
|
|
|
|
2024-01-19 18:05:42 +13:00
|
|
|
calculate :say_hello_to_george, :string do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
|
|
|
|
|
|
|
calculation fn records, _ ->
|
|
|
|
Enum.map(records, fn record ->
|
|
|
|
record.bio.say_hello
|
|
|
|
end)
|
2024-01-19 18:05:42 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
load bio: [say_hello: %{to: "George"}]
|
|
|
|
end
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :example_metadata, :string, Metadata do
|
|
|
|
public?(true)
|
|
|
|
end
|
2024-02-02 04:21:22 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :metadata_plus_metadata,
|
|
|
|
:string,
|
|
|
|
concat([:example_metadata, :example_metadata]) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2024-02-02 04:21:22 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :active, :boolean, expr(is_active) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
|
2020-12-01 17:18:39 +13:00
|
|
|
calculate :full_name, :string, {Concat, keys: [:first_name, :last_name]} do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
|
|
|
load([: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`.
|
2023-07-13 16:08:51 +12:00
|
|
|
argument(:separator, :string,
|
2021-01-16 14:41:21 +13:00
|
|
|
default: " ",
|
2024-03-28 09:06:40 +13:00
|
|
|
allow_expr?: true,
|
2021-01-16 14:41:21 +13:00
|
|
|
constraints: [allow_empty?: true, trim?: false]
|
2023-07-13 16:08:51 +12:00
|
|
|
)
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
2021-06-04 17:37:11 +12:00
|
|
|
|
2023-03-30 14:46:30 +13:00
|
|
|
calculate :full_name_with_select, :string, FullNameWithSelect do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-03-30 14:46:30 +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`.
|
2023-07-13 16:08:51 +12:00
|
|
|
argument(:separator, :string,
|
2023-03-30 14:46:30 +13:00
|
|
|
default: " ",
|
|
|
|
constraints: [allow_empty?: true, trim?: false]
|
2023-07-13 16:08:51 +12:00
|
|
|
)
|
2023-03-30 14:46:30 +13:00
|
|
|
end
|
|
|
|
|
2024-01-31 10:34:17 +13:00
|
|
|
calculate :full_name_with_select_plus_something,
|
|
|
|
:string,
|
2024-03-28 09:06:40 +13:00
|
|
|
expr(full_name_with_select <> "_something") do
|
|
|
|
public?(true)
|
|
|
|
end
|
2024-01-31 10:34:17 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :best_friends_best_friends_first_name, :string, BestFriendsBestFriendsFirstName do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-03-30 01:00:42 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :best_friends_first_name_plus_stuff,
|
|
|
|
:string,
|
|
|
|
BestFriendsFirstNamePlusStuff do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-12-31 08:40:36 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :best_friends_first_name_that_fails,
|
|
|
|
:string,
|
|
|
|
BestFriendsFirstNameThatFails do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-03-30 14:34:50 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :full_name_plus_first_name, :string, FullNamePlusFirstName do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-04-27 14:57:07 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :full_name_plus_full_name,
|
|
|
|
:string,
|
|
|
|
{ConcatWithLoad, keys: [:full_name, :full_name]} do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-10-04 17:22:40 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :full_name_plus_full_name_plus_full_name,
|
|
|
|
:string,
|
|
|
|
{ConcatWithLoad, keys: [:full_name, :full_name_plus_full_name]} do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-05-18 03:54:02 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :slug, :string, expr(full_name <> "123") do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
calculate :friends_names, :string, FriendsNames do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
calculate :expr_full_name, :string, expr(first_name <> " " <> last_name) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2021-06-04 17:37:11 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :string_join_full_name,
|
|
|
|
:string,
|
|
|
|
expr(string_join([first_name, last_name], " ")) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-02-22 03:40:58 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :string_join_full_name_ci,
|
|
|
|
:ci_string,
|
|
|
|
expr(string_join([first_name, last_name], ~i" ")) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2024-01-12 18:21:30 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :best_friends_name, :string, BestFriendsName do
|
|
|
|
public?(true)
|
|
|
|
end
|
2022-04-29 10:17:59 +12:00
|
|
|
|
2023-03-29 13:31:47 +13:00
|
|
|
calculate :names_of_best_friends_of_me, :string, NamesOfBestFriendsOfMe do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
argument(:only_special, :boolean, default: false)
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :name_with_users_name, :string, NameWithUsersName do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-02-22 13:05:40 +13:00
|
|
|
|
2023-03-01 04:03:35 +13:00
|
|
|
calculate :full_name_with_salutation,
|
|
|
|
:string,
|
|
|
|
expr(^arg(:salutation) <> " " <> conditional_full_name) do
|
2023-07-13 16:08:51 +12:00
|
|
|
argument(:salutation, :string, allow_nil?: false)
|
|
|
|
load([:conditional_full_name])
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-03-01 04:03:35 +13:00
|
|
|
end
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :conditional_full_name,
|
|
|
|
:string,
|
|
|
|
expr(
|
|
|
|
if(
|
|
|
|
not is_nil(first_name) and not is_nil(last_name),
|
|
|
|
first_name <> " " <> last_name,
|
|
|
|
"(none)"
|
|
|
|
)
|
|
|
|
) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-07-13 16:08:51 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :conditional_full_name_block,
|
|
|
|
:string,
|
|
|
|
expr(
|
|
|
|
if not is_nil(first_name) and not is_nil(last_name) do
|
|
|
|
first_name <> " " <> last_name
|
|
|
|
else
|
|
|
|
"(none)"
|
|
|
|
end
|
|
|
|
) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-07-13 16:08:51 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :conditional_full_name_cond,
|
|
|
|
:string,
|
|
|
|
expr(
|
|
|
|
cond do
|
|
|
|
not is_nil(first_name) and not is_nil(last_name) ->
|
|
|
|
first_name <> " " <> last_name
|
2023-07-13 16:08:51 +12:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
not is_nil(first_name) ->
|
|
|
|
first_name
|
|
|
|
|
|
|
|
true ->
|
|
|
|
"(none)"
|
|
|
|
end
|
|
|
|
) do
|
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
calculate :role_user_name_from_agg, :string, expr(role_user_name) do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-11-14 20:24:19 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :roles_have_user, :boolean, RolesHaveUser do
|
|
|
|
public?(true)
|
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
2022-04-29 10:17:59 +12:00
|
|
|
|
2022-12-31 08:40:36 +13:00
|
|
|
aggregates do
|
2024-03-28 09:06:40 +13:00
|
|
|
first :best_friends_first_name, :best_friend, :first_name do
|
|
|
|
public? true
|
|
|
|
end
|
2023-11-14 20:24:19 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
first :role_user_name, :roles, :user_name do
|
|
|
|
public? true
|
|
|
|
end
|
2022-12-31 08:40:36 +13:00
|
|
|
end
|
|
|
|
|
2022-04-29 10:17:59 +12:00
|
|
|
relationships do
|
2024-03-28 09:06:40 +13:00
|
|
|
belongs_to :best_friend, __MODULE__ do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-02-22 13:05:40 +13:00
|
|
|
|
|
|
|
has_many :best_friends_of_me, __MODULE__ do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
destination_attribute(:best_friend_id)
|
2023-02-22 13:05:40 +13:00
|
|
|
end
|
2023-03-29 13:31:47 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
has_many :roles, Ash.Test.CalculationTest.Role do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
|
2023-03-29 13:31:47 +13:00
|
|
|
many_to_many :friends, __MODULE__ do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-07-13 16:08:51 +12:00
|
|
|
through(FriendLink)
|
|
|
|
destination_attribute_on_join_resource(:target_id)
|
|
|
|
source_attribute_on_join_resource(:source_id)
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
2022-04-29 10:17:59 +12:00
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
|
|
|
|
2023-03-31 01:27:37 +13:00
|
|
|
defmodule ActorActive do
|
2024-03-28 09:06:40 +13:00
|
|
|
use Ash.Resource.Calculation
|
2023-03-31 01:27:37 +13:00
|
|
|
|
|
|
|
def load(_, _, _) do
|
|
|
|
[user: [:active], role: [:active]]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calculate(records, _, _) do
|
|
|
|
Enum.map(records, fn actor ->
|
|
|
|
if actor.role do
|
|
|
|
actor.role.active
|
|
|
|
else
|
|
|
|
actor.user.active
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defmodule Actor do
|
|
|
|
use Ash.Resource,
|
2024-03-28 09:06:40 +13:00
|
|
|
domain: Domain,
|
2023-03-31 01:27:37 +13:00
|
|
|
data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
actions do
|
2024-03-28 09:06:40 +13:00
|
|
|
default_accept :*
|
|
|
|
defaults([:read, :destroy, create: :*, update: :*])
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
ets do
|
2023-07-13 16:08:51 +12:00
|
|
|
private?(true)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
2023-07-13 16:08:51 +12:00
|
|
|
uuid_primary_key(:id)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
2024-03-28 09:06:40 +13:00
|
|
|
calculate :active, :boolean, ActorActive do
|
|
|
|
public?(true)
|
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
|
|
|
belongs_to :user, User do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
belongs_to :role, Role do
|
2024-03-28 09:06:40 +13:00
|
|
|
public?(true)
|
2023-03-31 01:27:37 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-04-11 10:26:42 +12:00
|
|
|
defmodule UserFirstNameForWarranty do
|
|
|
|
use Ash.Resource.Calculation
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
def load(_, _, _) do
|
|
|
|
[product: [user: [:first_name]]]
|
|
|
|
end
|
|
|
|
|
|
|
|
@impl true
|
2024-04-12 01:29:12 +12:00
|
|
|
def calculate(records, _, %{arguments: arguments}) do
|
|
|
|
if arguments.assert_strict? do
|
|
|
|
Enum.each(records, &assert(%Ash.NotLoaded{} = &1.product.user.last_name))
|
|
|
|
end
|
|
|
|
|
2024-04-11 10:26:42 +12:00
|
|
|
Enum.map(records, & &1.product.user.first_name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defmodule Product do
|
|
|
|
use Ash.Resource,
|
|
|
|
domain: Domain,
|
|
|
|
data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
actions do
|
|
|
|
default_accept :*
|
|
|
|
defaults([:read, :destroy, update: :*])
|
|
|
|
|
|
|
|
create :create do
|
|
|
|
primary? true
|
|
|
|
|
|
|
|
accept [:name, :user_id]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
uuid_primary_key(:id)
|
|
|
|
attribute :name, :string
|
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
|
|
|
belongs_to :user, User
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defmodule Warranty do
|
|
|
|
use Ash.Resource,
|
|
|
|
domain: Domain,
|
|
|
|
data_layer: Ash.DataLayer.Ets
|
|
|
|
|
|
|
|
actions do
|
|
|
|
default_accept :*
|
|
|
|
defaults([:read, :destroy, update: :*])
|
|
|
|
|
|
|
|
create :create do
|
|
|
|
primary? true
|
|
|
|
|
|
|
|
accept [:expiry, :product_id]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ets do
|
|
|
|
private?(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
attributes do
|
|
|
|
uuid_primary_key(:id)
|
|
|
|
attribute :expiry, :datetime
|
|
|
|
end
|
|
|
|
|
|
|
|
calculations do
|
|
|
|
calculate :user_first_name, :string, UserFirstNameForWarranty do
|
2024-04-12 01:29:12 +12:00
|
|
|
argument :assert_strict?, :boolean, default: false
|
2024-04-11 10:26:42 +12:00
|
|
|
public?(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
relationships do
|
|
|
|
belongs_to :product, Product
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
setup do
|
2022-04-29 10:17:59 +12:00
|
|
|
user1 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
|
|
first_name: "zach",
|
|
|
|
last_name: "daniel",
|
|
|
|
bio: %{greeting: "Yo! "}
|
|
|
|
})
|
|
|
|
|> Ash.create!()
|
2020-08-25 16:49:07 +12:00
|
|
|
|
2023-03-31 01:27:37 +13:00
|
|
|
admin_role =
|
|
|
|
Role
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "admin"})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2023-03-31 01:27:37 +13:00
|
|
|
|
|
|
|
normie_role =
|
|
|
|
Role
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "normie"})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2023-03-31 01:27:37 +13:00
|
|
|
|
2023-11-14 20:24:19 +13:00
|
|
|
actor1 =
|
|
|
|
Actor
|
|
|
|
|> Ash.Changeset.for_create(:create, %{user_id: user1.id, role_id: admin_role.id})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2023-03-31 01:27:37 +13:00
|
|
|
|
2022-04-29 10:17:59 +12:00
|
|
|
user2 =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "brian", last_name: "cranston"})
|
2022-09-20 07:44:06 +12:00
|
|
|
|> Ash.Changeset.manage_relationship(:best_friend, user1, type: :append_and_remove)
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:friends, user1, type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2020-08-25 16:49:07 +12:00
|
|
|
|
2023-11-14 20:24:19 +13:00
|
|
|
actor2 =
|
|
|
|
Actor
|
|
|
|
|> Ash.Changeset.for_create(:create, %{user_id: user2.id, role_id: normie_role.id})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2023-03-31 01:27:37 +13:00
|
|
|
|
2023-11-14 20:24:19 +13:00
|
|
|
%{
|
|
|
|
user1: user1,
|
|
|
|
user2: user2,
|
|
|
|
admin_role: admin_role,
|
|
|
|
normie_role: normie_role,
|
|
|
|
actor1: actor1,
|
|
|
|
actor2: actor2
|
|
|
|
}
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|
2023-10-10 11:50:15 +13:00
|
|
|
|
|
|
|
test "calculations can refer to `to_one` relationships in filters" do
|
|
|
|
Role
|
|
|
|
|> Ash.Query.filter(user_is_active_with_calc == true)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-10-10 11:50:15 +13:00
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
|
|
|
|
test "it uses default arguments" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:full_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2020-08-25 16:49:07 +12:00
|
|
|
|> Enum.map(& &1.full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian cranston", "zach daniel"]
|
|
|
|
end
|
|
|
|
|
2022-05-18 03:54:02 +12:00
|
|
|
test "loads dependencies", %{user1: user} do
|
2024-03-28 09:06:40 +13:00
|
|
|
assert %{slug: "zach daniel123"} = Ash.load!(user, [:slug])
|
2022-05-18 03:54:02 +12:00
|
|
|
end
|
|
|
|
|
2023-11-25 01:23:38 +13:00
|
|
|
test "calculations can depend on relationships directly" do
|
|
|
|
Role
|
|
|
|
|> Ash.Query.load(:has_user)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-11-25 01:23:38 +13:00
|
|
|
end
|
|
|
|
|
2024-02-26 02:24:19 +13:00
|
|
|
test "read actions can load calculations that use the actor", %{actor1: actor} do
|
|
|
|
user =
|
|
|
|
User
|
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "zach"})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2024-02-26 02:24:19 +13:00
|
|
|
|
|
|
|
Role
|
|
|
|
|> Ash.Changeset.for_create(:create, %{user_id: user.id})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2024-02-26 02:24:19 +13:00
|
|
|
|
|
|
|
assert [%{user_name: "zach"}] =
|
|
|
|
Role
|
|
|
|
|> Ash.Query.for_read(:by_user_name, %{user_name: "zach"}, actor: actor)
|
|
|
|
|> Ash.Query.load(:user_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2024-02-26 02:24:19 +13:00
|
|
|
end
|
|
|
|
|
2023-11-25 01:23:38 +13:00
|
|
|
test "calculations that depend on relationships directly can be loaded from elsewhere", %{
|
|
|
|
user1: user1,
|
|
|
|
admin_role: role
|
|
|
|
} do
|
|
|
|
role
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_update(:update)
|
2023-11-25 01:23:38 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:user, user1, type: :append)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.update!()
|
2023-11-25 01:23:38 +13:00
|
|
|
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(roles: :has_user)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-11-25 01:23:38 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "calculations can depend on calculations that depend on relationships directly", %{
|
|
|
|
user1: user1,
|
|
|
|
admin_role: role
|
|
|
|
} do
|
|
|
|
role
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_update(:update, %{})
|
2023-11-25 01:23:38 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:user, user1, type: :append)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.update!()
|
2023-11-25 01:23:38 +13:00
|
|
|
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:roles_have_user)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-11-25 01:23:38 +13:00
|
|
|
end
|
|
|
|
|
2023-02-24 03:45:27 +13:00
|
|
|
test "calculations can access the actor", %{user1: user1, user2: user2} do
|
|
|
|
assert %{
|
|
|
|
name_with_users_name: "zach daniel brian cranston"
|
|
|
|
} =
|
|
|
|
user1
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!(:name_with_users_name, actor: user2)
|
2023-02-24 03:45:27 +13:00
|
|
|
end
|
|
|
|
|
2022-04-27 14:57:07 +12:00
|
|
|
test "it loads anything specified by the load callback" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:full_name_plus_full_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-04-27 14:57:07 +12:00
|
|
|
|> Enum.map(& &1.full_name_plus_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian cranston brian cranston", "zach daniel zach daniel"]
|
|
|
|
end
|
|
|
|
|
2022-10-04 17:22:40 +13:00
|
|
|
test "it loads anything specified by the load callback, even when nested" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:full_name_plus_full_name_plus_full_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-10-04 17:22:40 +13:00
|
|
|
|> Enum.map(& &1.full_name_plus_full_name_plus_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == [
|
|
|
|
"brian cranston brian cranston brian cranston",
|
|
|
|
"zach daniel zach daniel zach daniel"
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
2022-07-12 13:31:50 +12:00
|
|
|
test "it reloads anything specified by the load callback if its already been loaded when using `lazy?: false`" do
|
|
|
|
full_names =
|
|
|
|
User
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Ash.Query.load([:full_name, :full_name_plus_full_name])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-07-12 13:31:50 +12:00
|
|
|
|> Enum.map(&%{&1 | full_name: &1.full_name <> " more"})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!(:full_name_plus_full_name)
|
2022-07-12 13:31:50 +12:00
|
|
|
|> Enum.map(& &1.full_name_plus_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == [
|
|
|
|
"brian cranston brian cranston",
|
|
|
|
"zach daniel zach daniel"
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
2022-04-29 10:17:59 +12:00
|
|
|
test "nested calculations are loaded if necessary" do
|
|
|
|
best_friends_names =
|
|
|
|
User
|
2022-12-31 08:40:36 +13:00
|
|
|
|> Ash.Query.load([:best_friends_name])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-04-29 10:17:59 +12:00
|
|
|
|> Enum.map(& &1.best_friends_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert best_friends_names == [nil, "zach daniel"]
|
|
|
|
end
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
test "calculations must specify required fields by default" do
|
|
|
|
assert_raise RuntimeError,
|
|
|
|
~r/Invalid return from calculation, expected a value, got \`%Ash.NotLoaded{}\`/,
|
|
|
|
fn ->
|
|
|
|
User
|
|
|
|
|> Ash.Query.load([:best_friends_first_name_that_fails])
|
|
|
|
|> Ash.read!()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-31 08:40:36 +13:00
|
|
|
test "nested aggregates are loaded if necessary" do
|
|
|
|
best_friends_first_names_plus_stuff =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load([:best_friends_first_name_plus_stuff])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2022-12-31 08:40:36 +13:00
|
|
|
|> Enum.map(& &1.best_friends_first_name_plus_stuff)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert best_friends_first_names_plus_stuff == [nil, "zach stuff"]
|
|
|
|
end
|
|
|
|
|
2020-08-25 16:49:07 +12:00
|
|
|
test "arguments can be supplied" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(full_name: %{separator: " - "})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2020-08-25 16:49:07 +12:00
|
|
|
|> 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: " - "})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-05-21 08:42:26 +12:00
|
|
|
|> 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
|
2024-03-30 04:14:12 +13:00
|
|
|
|> Ash.Query.calculate(:full_name, :string, {Concat, keys: [:first_name, :last_name]}, %{
|
2020-08-25 16:49:07 +12:00
|
|
|
separator: " \o.o/ "
|
|
|
|
})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2020-08-25 16:49:07 +12:00
|
|
|
|> 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)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-06-04 17:37:11 +12:00
|
|
|
|> 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)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-06-06 10:11:09 +12:00
|
|
|
|> 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)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-06-06 10:11:09 +12:00
|
|
|
|> Enum.map(& &1.expr_full_name)
|
|
|
|
|
|
|
|
assert full_names == ["zach daniel", "brian cranston"]
|
|
|
|
end
|
|
|
|
|
2023-03-01 04:03:35 +13:00
|
|
|
test "can sort on calculation in paginated read" do
|
|
|
|
full_names =
|
|
|
|
User
|
2023-03-01 04:11:48 +13:00
|
|
|
|> Ash.Query.for_read(:paginated)
|
2023-03-01 04:03:35 +13:00
|
|
|
|> Ash.Query.load(full_name_with_salutation: [salutation: "Mr"])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Query.sort(full_name_with_salutation: {%{salutation: "Mr"}, :asc})
|
|
|
|
|> Ash.read!()
|
2023-03-01 04:03:35 +13:00
|
|
|
|> Map.get(:results)
|
|
|
|
|> Enum.map(& &1.full_name_with_salutation)
|
|
|
|
|
2023-03-01 04:11:48 +13:00
|
|
|
assert full_names == ["Mr brian cranston", "Mr zach daniel"]
|
2023-03-01 04:03:35 +13:00
|
|
|
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
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "bob"})
|
|
|
|
|> Ash.create!()
|
2021-06-04 17:37:11 +12:00
|
|
|
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:conditional_full_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-06-04 17:37:11 +12:00
|
|
|
|> Enum.map(& &1.conditional_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["(none)", "brian cranston", "zach daniel"]
|
|
|
|
end
|
2021-11-14 07:48:25 +13:00
|
|
|
|
|
|
|
test "the `if` calculation can use the `do` style syntax" do
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "bob"})
|
|
|
|
|> Ash.create!()
|
2021-11-14 07:48:25 +13:00
|
|
|
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:conditional_full_name_block)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-11-14 07:48:25 +13:00
|
|
|
|> Enum.map(& &1.conditional_full_name_block)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["(none)", "brian cranston", "zach daniel"]
|
|
|
|
end
|
|
|
|
|
|
|
|
test "the `if` calculation can use the `cond` style syntax" do
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "bob"})
|
|
|
|
|> Ash.create!()
|
2021-11-14 07:48:25 +13:00
|
|
|
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:conditional_full_name_cond)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2021-11-14 07:48:25 +13:00
|
|
|
|> Enum.map(& &1.conditional_full_name_cond)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
2021-11-17 13:17:57 +13:00
|
|
|
assert full_names == ["bob", "brian cranston", "zach daniel"]
|
2021-11-14 07:48:25 +13:00
|
|
|
end
|
2023-02-22 03:40:58 +13:00
|
|
|
|
|
|
|
test "expression based calculations can handle lists of fields" do
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "bob"})
|
|
|
|
|> Ash.create!()
|
2023-02-22 03:40:58 +13:00
|
|
|
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:string_join_full_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-02-22 03:40:58 +13:00
|
|
|
|> Enum.map(& &1.string_join_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["bob", "brian cranston", "zach daniel"]
|
2024-01-12 18:21:30 +13:00
|
|
|
|
|
|
|
ci_full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:string_join_full_name_ci)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2024-01-12 18:21:30 +13:00
|
|
|
|> Enum.map(&Ash.CiString.value(&1.string_join_full_name_ci))
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert ci_full_names == ["bob", "brian cranston", "zach daniel"]
|
2023-02-22 03:40:58 +13:00
|
|
|
end
|
2023-02-22 13:05:40 +13:00
|
|
|
|
2024-03-25 12:25:13 +13:00
|
|
|
test "simple expression calculations will be run in memory" do
|
|
|
|
user =
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{first_name: "bob", last_name: "sagat"})
|
|
|
|
|> Ash.create!()
|
2024-03-25 12:25:13 +13:00
|
|
|
|
|
|
|
assert "george sagat" ==
|
|
|
|
user
|
|
|
|
|> Map.put(:first_name, "george")
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!(:string_join_full_name, reuse_values?: true)
|
2024-03-25 12:25:13 +13:00
|
|
|
|> Map.get(:string_join_full_name)
|
|
|
|
end
|
|
|
|
|
2023-03-29 13:31:47 +13:00
|
|
|
test "loading calculations with different relationship dependencies won't collide", %{
|
|
|
|
user1: %{id: user1_id} = user1
|
|
|
|
} do
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
|
|
first_name: "chidi",
|
|
|
|
last_name: "anagonye",
|
|
|
|
special: true
|
|
|
|
})
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:best_friend, user1, type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2023-03-29 13:31:47 +13:00
|
|
|
|
|
|
|
assert %{
|
|
|
|
calculations: %{
|
|
|
|
names_of_best_friends_of_me: "brian cranston - chidi anagonye",
|
|
|
|
names_of_special_best_friends_of_me: "chidi anagonye"
|
|
|
|
}
|
|
|
|
} =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(id == ^user1_id)
|
|
|
|
|> Ash.Query.load_calculation_as(
|
|
|
|
:names_of_best_friends_of_me,
|
|
|
|
:names_of_special_best_friends_of_me,
|
|
|
|
%{
|
|
|
|
only_special: true
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|> Ash.Query.load_calculation_as(
|
|
|
|
:names_of_best_friends_of_me,
|
|
|
|
:names_of_best_friends_of_me
|
|
|
|
)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read_one!()
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "loading calculations with different relationship dependencies won't collide, when manually loading one of the deps",
|
|
|
|
%{
|
|
|
|
user1: %{id: user1_id} = user1
|
|
|
|
} do
|
|
|
|
User
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
|
|
first_name: "chidi",
|
|
|
|
last_name: "anagonye",
|
|
|
|
special: true
|
|
|
|
})
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:best_friend, user1, type: :append_and_remove)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.create!()
|
2023-03-29 13:31:47 +13:00
|
|
|
|
|
|
|
assert %{
|
|
|
|
calculations: %{
|
|
|
|
names_of_best_friends_of_me: "brian cranston - chidi anagonye",
|
|
|
|
names_of_special_best_friends_of_me: "chidi anagonye"
|
|
|
|
}
|
|
|
|
} =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(id == ^user1_id)
|
|
|
|
|> Ash.Query.load(:best_friends_of_me)
|
|
|
|
|> Ash.Query.load_calculation_as(
|
|
|
|
:names_of_best_friends_of_me,
|
|
|
|
:names_of_special_best_friends_of_me,
|
|
|
|
%{
|
|
|
|
only_special: true
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|> Ash.Query.load_calculation_as(
|
|
|
|
:names_of_best_friends_of_me,
|
|
|
|
:names_of_best_friends_of_me
|
|
|
|
)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read_one!()
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "calculations that depend on many to many relationships will load", %{user2: user2} do
|
|
|
|
assert %{
|
|
|
|
friends_names: "zach daniel"
|
|
|
|
} =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(id == ^user2.id)
|
|
|
|
|> Ash.Query.load(:friends_names)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read_one!()
|
2023-03-29 13:31:47 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
test "when already loading a calculation's dependency it is used" do
|
|
|
|
full_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load([:full_name, :full_name_plus_full_name])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Enum.map(& &1.full_name_plus_full_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert full_names == ["brian cranston brian cranston", "zach daniel zach daniel"]
|
|
|
|
end
|
|
|
|
|
|
|
|
test "when already loading a nested calculation dependency, it is used" do
|
|
|
|
best_friends_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load([:best_friends_name, best_friend: [:full_name]])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-03-29 13:31:47 +13:00
|
|
|
|> Enum.map(& &1.best_friends_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert best_friends_names == [nil, "zach daniel"]
|
|
|
|
end
|
2023-03-30 01:00:42 +13:00
|
|
|
|
|
|
|
test "loading a nested aggregate works" do
|
|
|
|
best_friends_best_friends_first_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(:best_friends_best_friends_first_name)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-03-30 01:00:42 +13:00
|
|
|
|> Enum.map(& &1.best_friends_best_friends_first_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert best_friends_best_friends_first_names == ["bf: ", "bf: "]
|
|
|
|
end
|
|
|
|
|
|
|
|
test "loading a nested aggregate works even when its already being loaded" do
|
|
|
|
best_friends_best_friends_first_names =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load([
|
|
|
|
:best_friends_best_friends_first_name,
|
|
|
|
best_friend: [best_friend: [:first_name]]
|
|
|
|
])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-03-30 01:00:42 +13:00
|
|
|
|> Enum.map(& &1.best_friends_best_friends_first_name)
|
|
|
|
|> Enum.sort()
|
|
|
|
|
|
|
|
assert best_friends_best_friends_first_names == ["bf: ", "bf: "]
|
|
|
|
end
|
2023-03-30 14:34:50 +13:00
|
|
|
|
|
|
|
test "loading a calculation with selects that loads a calculation with selects works" do
|
2023-04-01 05:36:20 +13:00
|
|
|
assert ["brian cranston brian", "zach daniel zach"] ==
|
2023-03-30 14:46:30 +13:00
|
|
|
User
|
|
|
|
|> Ash.Query.select([])
|
|
|
|
|> Ash.Query.load([
|
|
|
|
:full_name_plus_first_name
|
|
|
|
])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2023-03-30 14:46:30 +13:00
|
|
|
|> Enum.map(& &1.full_name_plus_first_name)
|
|
|
|
|> Enum.sort()
|
2023-03-30 14:34:50 +13:00
|
|
|
end
|
2023-03-31 01:27:37 +13:00
|
|
|
|
|
|
|
test "nested calculations that depend on aggregates work" do
|
|
|
|
assert [false, false] =
|
|
|
|
Actor
|
|
|
|
|> Ash.Query.load(:active)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!(authorize?: true)
|
2023-03-31 01:27:37 +13:00
|
|
|
|> Enum.map(& &1.active)
|
|
|
|
end
|
2023-04-08 19:39:41 +12:00
|
|
|
|
2023-11-14 20:24:19 +13:00
|
|
|
test "aggregates using calculations pass actor to calculations", %{
|
|
|
|
user1: user,
|
|
|
|
actor1: actor,
|
|
|
|
admin_role: role
|
|
|
|
} do
|
|
|
|
user1 = user
|
|
|
|
|
|
|
|
role
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.Changeset.for_update(:update, %{}, actor: actor)
|
2023-11-14 20:24:19 +13:00
|
|
|
|> Ash.Changeset.manage_relationship(:user, user1, type: :append)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.update!(actor: actor)
|
2023-11-14 20:24:19 +13:00
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
roles = Role |> Ash.Query.load(:user_name) |> Ash.read!(actor: actor)
|
2023-11-14 20:24:19 +13:00
|
|
|
|
|
|
|
zach_role =
|
|
|
|
Enum.find(roles, fn role ->
|
|
|
|
role.user_name == "zach"
|
|
|
|
end)
|
|
|
|
|
|
|
|
assert zach_role.user_name == "zach"
|
|
|
|
|
|
|
|
users =
|
|
|
|
User
|
|
|
|
|> Ash.Query.select([])
|
2023-11-17 02:18:39 +13:00
|
|
|
|> Ash.Query.load([:role_user_name, :role_user_name_from_agg])
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!(actor: actor)
|
2023-11-14 20:24:19 +13:00
|
|
|
|
|
|
|
zach_user =
|
|
|
|
Enum.find(users, fn user ->
|
|
|
|
user.id == zach_role.user_id
|
|
|
|
end)
|
|
|
|
|
|
|
|
assert zach_user.role_user_name == "zach"
|
2023-11-17 02:18:39 +13:00
|
|
|
assert zach_user.role_user_name_from_agg == "zach"
|
2023-11-14 20:24:19 +13:00
|
|
|
end
|
|
|
|
|
2023-04-08 19:39:41 +12:00
|
|
|
test "invalid calculation arguments show errors in the query" do
|
|
|
|
assert %{valid?: false} =
|
|
|
|
User
|
|
|
|
|> Ash.Query.load(full_name: %{separator: %{foo: :bar}})
|
|
|
|
end
|
2024-01-19 18:05:42 +13:00
|
|
|
|
|
|
|
test "calculation dependencies with conflicting load throughs still receive the appropriate values",
|
|
|
|
%{user1: user1} do
|
|
|
|
user1
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!([:say_hello_to_fred, :say_hello_to_george])
|
2024-01-19 18:05:42 +13:00
|
|
|
end
|
2024-01-31 10:34:17 +13:00
|
|
|
|
2024-03-25 16:25:28 +13:00
|
|
|
test "calculation dependencies can detect non-loaded nested values", %{user1: user1} do
|
|
|
|
greeting =
|
|
|
|
user1
|
|
|
|
|> Map.update!(:bio, fn bio ->
|
|
|
|
%{bio | greeting: "A new value it definitely wasn't before!"}
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!([:bio_greeting], reuse_values?: true)
|
2024-03-25 16:25:28 +13:00
|
|
|
|> Map.get(:bio_greeting)
|
|
|
|
|
|
|
|
assert greeting == "A new value it definitely wasn't before!"
|
|
|
|
|
|
|
|
greeting =
|
|
|
|
user1
|
|
|
|
|> Map.update!(:bio, fn bio ->
|
|
|
|
%{bio | greeting: %Ash.NotLoaded{}}
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!([:bio_greeting], reuse_values?: true)
|
2024-03-25 16:25:28 +13:00
|
|
|
|> Map.get(:bio_greeting)
|
|
|
|
|
|
|
|
assert greeting == "Yo!"
|
|
|
|
end
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
test "calculation dependencies are reused if `reuse_values?` is `true`", %{user1: user1} do
|
2024-03-25 16:25:28 +13:00
|
|
|
greeting =
|
|
|
|
user1
|
|
|
|
|> Map.update!(:bio, fn bio ->
|
|
|
|
%{bio | greeting: "A new value it definitely wasn't before!"}
|
|
|
|
end)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!([:bio_greeting], reuse_values?: true)
|
2024-03-25 16:25:28 +13:00
|
|
|
|> Map.get(:bio_greeting)
|
|
|
|
|
2024-03-28 09:06:40 +13:00
|
|
|
assert greeting == "A new value it definitely wasn't before!"
|
2024-03-25 16:25:28 +13:00
|
|
|
end
|
|
|
|
|
2024-01-31 10:34:17 +13:00
|
|
|
test "expression calculations that depend on runtime calculations work", %{user1: user1} do
|
|
|
|
assert [%{full_name_with_select_plus_something: "zach daniel_something"}] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(id == ^user1.id)
|
|
|
|
|> Ash.Query.load(:full_name_with_select_plus_something)
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.read!()
|
2024-01-31 10:34:17 +13:00
|
|
|
end
|
2024-02-02 04:21:22 +13:00
|
|
|
|
|
|
|
test "calculations that extract metadata will be loaded as a dependency of the concat calculation",
|
|
|
|
%{user1: user1} do
|
|
|
|
assert "example metadataexample metadata" ==
|
|
|
|
user1
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.load!(:metadata_plus_metadata)
|
2024-02-02 04:21:22 +13:00
|
|
|
|> Map.get(:metadata_plus_metadata)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "metadata is persisted after an update", %{user1: user1} do
|
|
|
|
assert %{example_metadata: "example metadata"} =
|
|
|
|
user1.__metadata__
|
|
|
|
|
|
|
|
assert %{example_metadata: "example metadata"} =
|
|
|
|
user1
|
|
|
|
|> Ash.Changeset.for_update(:update, %{first_name: "something new"})
|
2024-03-28 09:06:40 +13:00
|
|
|
|> Ash.update!()
|
2024-02-02 04:21:22 +13:00
|
|
|
|> Map.get(:__metadata__)
|
|
|
|
end
|
2024-03-28 09:06:40 +13:00
|
|
|
|
|
|
|
test "arguments can be referenced in calculations", %{user1: user1} do
|
|
|
|
user1_id = user1.id
|
|
|
|
|
|
|
|
assert [%{id: ^user1_id}] =
|
|
|
|
User
|
|
|
|
|> Ash.Query.filter(full_name(separator: first_name) == "zachzachdaniel")
|
|
|
|
|> Ash.read!()
|
|
|
|
end
|
2024-04-11 10:26:42 +12:00
|
|
|
|
|
|
|
test "loads private relationships", %{user1: user1} do
|
|
|
|
product =
|
|
|
|
Product
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "SuperFoo3000", user_id: user1.id})
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
warranty =
|
|
|
|
Warranty
|
|
|
|
|> Ash.Changeset.for_create(:create, %{product_id: product.id})
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
assert %{user_first_name: "zach"} = Ash.load!(warranty, :user_first_name)
|
|
|
|
end
|
2024-04-12 01:29:12 +12:00
|
|
|
|
|
|
|
test "applies strict_loads? also to nested relationships", %{user1: user1} do
|
|
|
|
product =
|
|
|
|
Product
|
|
|
|
|> Ash.Changeset.for_create(:create, %{name: "SuperFoo3000", user_id: user1.id})
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
warranty =
|
|
|
|
Warranty
|
|
|
|
|> Ash.Changeset.for_create(:create, %{product_id: product.id})
|
|
|
|
|> Ash.create!()
|
|
|
|
|
|
|
|
Ash.load!(warranty, user_first_name: [assert_strict?: true])
|
|
|
|
end
|
2020-08-25 16:49:07 +12:00
|
|
|
end
|