Packages

ash

1.39.3
3.29.3 3.29.2 3.29.1 3.29.0 3.28.0 3.27.8 3.27.7 3.27.6 3.27.5 3.27.4 3.27.3 3.27.2 3.27.1 3.27.0 3.26.0 3.25.2 3.25.1 3.25.0 retired 3.24.7 3.24.6 3.24.5 3.24.4 3.24.3 3.24.2 3.24.1 3.24.0 3.23.1 3.23.0 3.22.2 3.22.1 3.22.0 3.21.3 3.21.2 3.21.1 3.21.0 3.20.0 3.19.3 3.19.2 3.19.1 3.19.0 3.18.0 3.17.1 3.17.0 3.16.0 3.15.0 3.14.1 3.14.0 retired 3.13.2 3.13.1 3.13.0 3.12.0 3.11.3 3.11.2 3.11.1 3.11.0 3.10.1 3.10.0 3.9.0 3.8.0 3.7.6 3.7.5 3.7.4 3.7.3 3.7.2 3.7.1 3.7.0 retired 3.6.3 retired 3.6.2 3.6.1 3.6.0 3.5.43 3.5.42 3.5.41 3.5.40 3.5.39 3.5.38 3.5.37 3.5.36 3.5.35 3.5.34 3.5.33 3.5.32 3.5.31 3.5.30 3.5.29 3.5.28 3.5.27 3.5.26 3.5.25 3.5.24 3.5.23 3.5.22 3.5.21 3.5.20 3.5.19 3.5.18 3.5.17 3.5.16 3.5.15 3.5.14 3.5.13 3.5.12 3.5.11 3.5.10 3.5.9 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.74 retired 3.4.73 3.4.72 3.4.71 3.4.70 3.4.69 3.4.68 3.4.67 3.4.66 3.4.65 3.4.64 3.4.63 3.4.62 3.4.61 3.4.60 3.4.59 3.4.58 3.4.57 3.4.56 3.4.55 3.4.54 3.4.53 3.4.52 3.4.51 3.4.50 3.4.49 3.4.48 3.4.47 3.4.46 3.4.45 3.4.44 3.4.43 3.4.42 3.4.41 3.4.40 3.4.39 3.4.38 3.4.37 3.4.36 3.4.35 3.4.34 3.4.33 3.4.32 3.4.31 3.4.30 3.4.29 3.4.28 3.4.27 3.4.26 3.4.25 3.4.24 3.4.23 3.4.22 3.4.21 3.4.20 3.4.19 3.4.18 3.4.17 3.4.16 3.4.15 3.4.14 3.4.13 3.4.12 3.4.11 3.4.10 3.4.9 3.4.8 3.4.7 3.4.6 3.4.5 3.4.4 3.4.3 3.4.2 3.4.1 3.4.0 3.3.3 3.3.2 3.3.1 3.3.0 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 3.2.0 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.16 3.0.15 3.0.14 3.0.13 3.0.12 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 3.0.0-rc.46 3.0.0-rc.45 3.0.0-rc.44 3.0.0-rc.43 3.0.0-rc.42 3.0.0-rc.41 3.0.0-rc.40 3.0.0-rc.39 3.0.0-rc.38 3.0.0-rc.37 3.0.0-rc.36 3.0.0-rc.35 3.0.0-rc.34 3.0.0-rc.33 3.0.0-rc.32 3.0.0-rc.31 3.0.0-rc.29 3.0.0-rc.28 3.0.0-rc.27 3.0.0-rc.26 3.0.0-rc.25 3.0.0-rc.24 3.0.0-rc.23 3.0.0-rc.22 3.0.0-rc.21 3.0.0-rc.20 3.0.0-rc.19 3.0.0-rc.18 3.0.0-rc.17 3.0.0-rc.16 3.0.0-rc.15 3.0.0-rc.14 3.0.0-rc.13 3.0.0-rc.12 3.0.0-rc.11 3.0.0-rc.10 3.0.0-rc.9 3.0.0-rc.8 3.0.0-rc.7 3.0.0-rc.6 3.0.0-rc.5 3.0.0-rc.4 3.0.0-rc.3 3.0.0-rc.1 3.0.0-rc.0 2.21.15 2.21.14 2.21.13 2.21.12 2.21.11 2.21.10 2.21.9 2.21.8 2.21.7 2.21.6 2.21.5 2.21.4 2.21.3 2.21.2 2.21.1 2.21.0 2.20.3 2.20.2 2.20.1 2.20.0 2.19.14 2.19.13 2.19.12 2.19.11 2.19.10 2.19.9 2.19.8 2.19.7 2.19.6 2.19.5 2.19.4 2.19.3 retired 2.19.2 retired 2.19.1 retired 2.19.0 retired 2.18.2 2.18.1 2.18.0 2.17.24 2.17.23 2.17.22 2.17.21 2.17.20 2.17.19 2.17.18 2.17.17 2.17.16 2.17.15 2.17.14 2.17.13 2.17.12 2.17.11 2.17.10 2.17.9 2.17.8 2.17.7 2.17.6 2.17.5 2.17.4 2.17.3 2.17.2 2.17.1 2.17.0 2.16.1 2.16.0 2.15.20 2.15.19 2.15.18 2.15.17 2.15.16 2.15.15 2.15.14 2.15.13 2.15.12 2.15.11 2.15.10 2.15.9 2.15.8 2.15.7 2.15.6 2.15.5 2.15.4 2.15.2 2.15.1 2.15.0 2.14.21 2.14.20 2.14.19 2.14.18 2.14.17 2.14.16 2.14.15 2.14.14 2.14.13 2.14.12 2.14.11 2.14.10 2.14.9 2.14.8 2.14.7 2.14.6 2.14.5 2.14.4 2.14.3 2.14.2 2.14.1 2.14.0 2.13.4 retired 2.13.3 2.13.2 2.13.1 2.13.0 2.12.1 2.12.0 2.11.11 2.11.10 2.11.9 2.11.8 2.11.7 2.11.6 2.11.5 2.11.4 2.11.3 2.11.2 2.11.1 2.11.0 2.11.0-rc.3 2.11.0-rc.2 2.11.0-rc.1 2.11.0-rc.0 2.10.2 2.10.1 2.10.0 2.9.29 2.9.28 2.9.27 2.9.26 2.9.25 2.9.24 2.9.23 2.9.22 2.9.21 2.9.20 2.9.19 2.9.18 2.9.17 2.9.16 2.9.15 2.9.14 2.9.13 2.9.12 2.9.11 2.9.10 2.9.9 2.9.8 2.9.7 2.9.6 2.9.5 2.9.4 2.9.3 2.9.2 2.9.1 2.9.0 2.8.1 2.8.0 2.7.1 2.7.0 2.6.31 2.6.30 2.6.29 2.6.28 2.6.27 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.16 2.5.15 2.5.14 2.5.13 2.5.12 2.5.11 2.5.10 2.5.9 2.5.8 2.5.7 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.5.0-rc.6 2.5.0-rc.5 2.5.0-rc.4 2.5.0-rc.3 2.5.0-rc.2 2.5.0-rc.1 2.5.0-rc.0 2.4.30 2.4.29 2.4.28 2.4.27 2.4.26 2.4.25 2.4.24 2.4.23 2.4.22 2.4.21 2.4.20 2.4.19 2.4.18 2.4.17 2.4.16 2.4.15 2.4.14 2.4.13 2.4.12 2.4.11 2.4.10 2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 2.0.0-rc.15 2.0.0-rc.14 2.0.0-rc.13 2.0.0-rc.12 2.0.0-rc.11 2.0.0-rc.10 2.0.0-rc.9 2.0.0-rc.8 2.0.0-rc.7 2.0.0-rc.6 2.0.0-rc.5 2.0.0-rc.4 2.0.0-rc.3 2.0.0-rc.2 2.0.0-rc.1 2.0.0-rc.0 2.0.0-pre.8 2.0.0-pre.7 2.0.0-pre.6 2.0.0-pre.5 2.0.0-pre.4 2.0.0-pre.3 2.0.0-pre.2 2.0.0-pre.1 2.0.0-pre.0 1.53.3 1.53.2 1.53.0 1.52.0-rc.22 1.52.0-rc.21 1.52.0-rc.20 1.52.0-rc.19 1.52.0-rc.18 1.52.0-rc.17 1.52.0-rc.16 1.52.0-rc.15 1.52.0-rc.14 1.52.0-rc.13 1.52.0-rc.12 1.52.0-rc.11 1.52.0-rc.10 1.52.0-rc.9 1.52.0-rc.8 1.52.0-rc.7 1.52.0-rc.6 1.52.0-rc.5 1.52.0-rc.4 1.52.0-rc.3 1.52.0-rc.2 1.52.0-rc.1 1.52.0-rc.0 1.51.2 1.51.1 retired 1.51.0 1.50.21 1.50.20 1.50.19 1.50.18 1.50.17 1.50.16 1.50.15 1.50.14 1.50.13 1.50.12 1.50.11 1.50.10 1.50.9 1.50.8 1.50.7 1.50.6 1.50.5 1.50.4 1.50.3 1.50.2 1.50.1 1.50.0 1.49.0 1.48.0-rc.30 1.48.0-rc.29 1.48.0-rc.28 1.48.0-rc.27 1.48.0-rc.26 1.48.0-rc.25 1.48.0-rc.24 1.48.0-rc.23 1.48.0-rc.22 1.48.0-rc.21 1.48.0-rc.20 1.48.0-rc.19 1.48.0-rc.18 1.48.0-rc.17 1.48.0-rc.16 1.48.0-rc.15 1.48.0-rc.14 1.48.0-rc.13 1.48.0-rc.12 1.48.0-rc.11 1.48.0-rc.10 1.48.0-rc.9 1.48.0-rc.8 1.48.0-rc.7 1.48.0-rc.6 1.48.0-rc.5 1.48.0-rc.4 1.48.0-rc.3 1.48.0-rc.2 1.48.0-rc.1 1.48.0-rc.0 1.47.12 1.47.11 1.47.10 1.47.9 1.47.8 1.47.7 1.47.6 1.47.5 1.47.4 1.47.3 1.47.2 1.47.1 1.47.0 1.46.13 1.46.12 1.46.11 1.46.10 1.46.9 1.46.8 1.46.7 1.46.6 1.46.5 1.46.4 1.46.3 1.46.2 1.46.1 1.46.0 1.45.0-rc9 1.45.0-rc8 1.45.0-rc7 1.45.0-rc6 1.45.0-rc5 1.45.0-rc4 1.45.0-rc3 1.45.0-rc20 1.45.0-rc2 1.45.0-rc19 1.45.0-rc18 1.45.0-rc17 1.45.0-rc16 1.45.0-rc15 1.45.0-rc14 1.45.0-rc13 1.45.0-rc12 1.45.0-rc11 1.45.0-rc10 1.45.0-rc1 1.45.0-rc0 1.44.13 1.44.12 1.44.11 1.44.10 1.44.9 1.44.8 1.44.7 1.44.6 1.44.5 1.44.4 1.44.3 1.44.2 1.44.1 1.44.0 1.43.12 1.43.11 1.43.10 1.43.9 1.43.8 1.43.7 1.43.6 1.43.5 1.43.4 1.43.3 1.43.2 1.43.1 1.43.0 1.42.0 1.41.12 1.41.11 1.41.10 1.41.9 1.41.8 1.41.7 1.41.6 1.41.5 1.41.4 1.41.3 1.41.2 1.41.1 1.41.0 1.40.0 1.39.7 1.39.6 1.39.5 1.39.4 1.39.3 1.39.2 1.39.1 1.39.0 1.38.0 1.37.2 1.37.1 1.37.0 1.36.22 1.36.21 1.36.19 1.36.18 1.36.17 1.36.16 1.36.15 1.36.14 1.36.13 1.36.12 1.36.11 1.36.10 1.36.9 1.36.8 1.36.7 1.36.6 1.36.5 1.36.4 1.36.3 1.36.2 1.36.0 1.35.1 1.35.0 1.34.9 1.34.8 1.34.7 1.34.6 1.34.5 1.34.4 1.34.3 1.34.2 1.34.1 1.34.0 1.33.0 1.32.2 1.32.1 1.32.0 1.31.1 1.31.0 1.30.2 1.30.1 1.29.0-rc1 1.29.0-rc0 1.28.1 1.28.0 1.27.1 1.27.0 1.26.13 1.26.12 1.26.11 1.26.10 1.26.9 1.26.8 1.26.7 1.26.6 1.26.5 1.26.4 1.26.2 1.26.1 1.26.0 1.25.8 1.25.7 1.25.6 1.25.5 1.25.4 1.25.3 1.25.2 1.25.1 1.25.0 1.24.2 1.24.1 1.24.0 1.23.3 1.23.2 1.23.1 1.23.0 1.22.1 1.22.0 1.20.1 1.20.0 1.19.1 1.19.0 1.18.1 1.18.0 1.17.1 1.17.0 1.16.2 1.15.1 1.15.0 1.14.0 1.13.4 1.13.3 1.13.2 1.13.1 1.13.0 1.12.0 1.11.1 1.11.0 1.10.0 1.9.0 1.8.0 1.7.0 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.1 1.5.0 1.4.1 1.4.0 1.3.1 1.3.0 1.2.1 1.2.0 1.1.3 1.1.2 1.1.0 1.0.3 1.0.2 1.0.1 1.0.0 0.13.1 0.13.0 0.12.0 0.10.0 0.9.1 0.9.0 0.8.0 0.7.0 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.2 0.5.1 0.5.0 0.4.0 0.3.0 0.2.0 0.1.9 0.1.8 0.1.3 0.1.1 0.1.0

A declarative, extensible framework for building Elixir applications.

Security advisory: This version has known vulnerabilities. View advisories

Current section

Files

Jump to
ash lib ash embeddable_type.ex
Raw

lib/ash/embeddable_type.ex

defmodule Ash.EmbeddableType do
@moduledoc false
@embedded_resource_array_constraints [
sort: [
type: :any,
doc: """
A sort to be applied when casting the data.
Only relevant for a type of {:array, `EmbeddedResource}`
The sort is not applied when reading the data, so if the sort changes you will
need to fix it in your database or wait for the data to be written again, at which
point it will be sorted when casting.
"""
],
load: [
type: {:list, :atom},
doc: """
A list of calculations to load on the resource.
Only relevant for a type of {:array, `EmbeddedResource}`
Aggregates are not supported on embedded resources.
"""
],
create_action: [
type: :atom,
doc:
"The action to use on the resource when creating an embed. The primary is used by default."
],
update_action: [
type: :atom,
doc:
"The action to use on the resource when updating an embed. The primary is used by default."
],
destroy_action: [
type: :atom,
doc:
"The action to use on the resource when destroying an embed. The primary is used by default."
]
]
@doc false
def embedded_resource_array_constraints, do: @embedded_resource_array_constraints
@doc false
def handle_errors(errors) do
errors
|> do_handle_errors()
|> List.wrap()
|> Ash.Error.flatten_preserving_keywords()
end
defp do_handle_errors(errors) when is_list(errors) do
if Keyword.keyword?(errors) do
main_fields = Keyword.take(errors, [:message, :field, :fields])
vars = Keyword.merge(main_fields, Keyword.get(errors, :vars, []))
main_fields
|> Keyword.put(:vars, vars)
|> Enum.into(%{})
|> do_handle_errors()
else
Enum.map(errors, &do_handle_errors/1)
end
end
defp do_handle_errors(%{errors: errors}) do
errors
|> List.wrap()
|> do_handle_errors()
end
defp do_handle_errors(%Ash.Error.Changes.InvalidAttribute{
message: message,
field: field,
vars: vars
}) do
vars
|> Keyword.put(:field, field)
|> Keyword.put(:message, message)
|> add_index()
end
defp do_handle_errors(%{message: message, vars: vars, field: field}) do
vars
|> Keyword.put(:message, message)
|> Keyword.put(:field, field)
|> add_index()
end
defp do_handle_errors(%{message: message, vars: vars}) do
vars
|> Keyword.put(:message, message)
|> add_index()
end
defp do_handle_errors(%{field: field} = exception) do
[field: field, message: Exception.message(exception)]
end
defp do_handle_errors(error) when is_binary(error) do
[message: error]
end
defp do_handle_errors(error) when is_exception(error) do
[message: Exception.message(error)]
end
defp do_handle_errors(_error) do
[message: "Something went wrong"]
end
defp add_index(opts) do
opts
# cond do
# opts[:index] && opts[:field] ->
# Keyword.put(opts, :field, "#{opts[:field]}[#{opts[:index]}]")
# opts[:index] ->
# Keyword.put(opts, :field, "[#{opts[:index]}]")
# true ->
# opts
# end
end
defmacro single_embed_implementation do
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
alias __MODULE__.ShadowApi
def storage_type, do: :map
def cast_input(%{__struct__: __MODULE__} = input, _constraints), do: {:ok, input}
def cast_input(value, constraints) when is_map(value) do
action =
constraints[:create_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :create).name
__MODULE__
|> Ash.Changeset.for_create(action, value)
|> ShadowApi.create()
|> case do
{:ok, result} ->
{:ok, result}
{:error, error} ->
{:error, Ash.EmbeddableType.handle_errors(error)}
end
end
def cast_input(nil, _), do: {:ok, nil}
def cast_input(_, _), do: :error
def cast_stored(value, constraints) when is_map(value) do
__MODULE__
|> Ash.Resource.Info.attributes()
|> Enum.reduce_while({:ok, struct(__MODULE__)}, fn attr, {:ok, struct} ->
with {:fetch, {:ok, value}} <- {:fetch, fetch_key(value, attr.name)},
{:ok, casted} <- Ash.Type.cast_stored(attr.type, value, constraints) do
{:cont, {:ok, Map.put(struct, attr.name, casted)}}
else
{:fetch, :error} ->
{:cont, {:ok, struct}}
other ->
{:halt, other}
end
end)
end
def cast_stored(nil, _), do: {:ok, nil}
def cast_stored(_other, _) do
:error
end
def fetch_key(map, atom) do
case Map.fetch(map, atom) do
{:ok, value} ->
value
:error ->
Map.fetch(map, to_string(atom))
end
end
def dump_to_native(value, _) when is_map(value) do
attributes = Ash.Resource.Info.attributes(__MODULE__)
calculations = Ash.Resource.Info.calculations(__MODULE__)
{:ok, Map.take(value, Enum.map(attributes ++ calculations, & &1.name))}
end
def dump_to_native(nil, _), do: {:ok, nil}
def dump_to_native(_, _), do: :error
def constraints,
do:
Keyword.take(array_constraints(), [
:load,
:create_action,
:destroy_action,
:update_action
])
def apply_constraints(nil, _), do: {:ok, nil}
def apply_constraints(term, constraints) do
__MODULE__
|> Ash.Query.put_context(:data, [term])
|> Ash.Query.load(constraints[:load] || [])
|> ShadowApi.read()
|> case do
{:ok, [result]} ->
{:ok, result}
{:error, errors} ->
{:error, Ash.EmbeddableType.handle_errors(errors)}
end
end
def handle_change(nil, new_value, _constraints) do
{:ok, new_value}
end
def handle_change(old_value, nil, constraints) do
action =
constraints[:destroy_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :destroy).name
case ShadowApi.destroy(old_value, action: action) do
:ok -> {:ok, nil}
{:error, error} -> {:error, Ash.EmbeddableType.handle_errors(error)}
end
end
def handle_change(old_value, new_value, constraints) do
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
if Enum.all?(pkey_fields, fn pkey_field ->
Ash.Resource.Info.attribute(__MODULE__, pkey_field).private?
end) do
{:ok, new_value}
else
pkey = Map.take(old_value, pkey_fields)
if Map.take(new_value, pkey_fields) == pkey do
{:ok, new_value}
else
action =
constraints[:destroy_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :destroy).name
case ShadowApi.destroy(old_value, action: action) do
:ok -> {:ok, new_value}
{:error, error} -> {:error, Ash.EmbeddableType.handle_errors(error)}
end
end
end
end
def prepare_change(old_value, "", constraints) do
prepare_change(old_value, nil, constraints)
end
def prepare_change(_old_value, nil, _constraints) do
{:ok, nil}
end
def prepare_change(_old_value, %{__struct__: __MODULE__} = new_value, _constraints) do
{:ok, new_value}
end
def prepare_change(nil, new_value, _constraints) do
{:ok, new_value}
end
def prepare_change(old_value, new_uncasted_value, constraints) do
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
if Enum.all?(pkey_fields, fn pkey_field ->
Ash.Resource.Info.attribute(__MODULE__, pkey_field).private?
end) do
action =
constraints[:update_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :update).name
old_value
|> Ash.Changeset.for_update(action, new_uncasted_value)
|> ShadowApi.update()
|> case do
{:ok, value} -> {:ok, value}
{:error, error} -> {:error, Ash.EmbeddableType.handle_errors(error)}
end
else
pkey =
Enum.into(pkey_fields, %{}, fn pkey_field ->
case fetch_key(new_uncasted_value, pkey_field) do
:error ->
{pkey_field, :error}
value ->
attribute = Ash.Resource.Info.attribute(__MODULE__, pkey_field)
case Ash.Type.cast_input(attribute.type, value, attribute.constraints) do
{:ok, casted} ->
{pkey_field, casted}
_ ->
{pkey_field, :error}
end
end
end)
if Enum.any?(Map.values(pkey), &(&1 == :error)) do
{:ok, new_uncasted_value}
else
old_pkey = Map.take(old_value, pkey_fields)
if old_pkey == pkey do
action =
constraints[:update_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :update).name
old_value
|> Ash.Changeset.for_update(action, new_uncasted_value)
|> ShadowApi.update()
|> case do
{:ok, value} -> {:ok, value}
{:error, error} -> {:error, Ash.EmbeddableType.handle_errors(error)}
end
else
{:ok, new_uncasted_value}
end
end
end
end
end
end
defmacro array_embed_implementation do
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
alias __MODULE__.ShadowApi
def array_constraints, do: Ash.EmbeddableType.embedded_resource_array_constraints()
def apply_constraints_array([], _constraints), do: {:ok, []}
def apply_constraints_array(term, constraints) do
pkey = Ash.Resource.Info.primary_key(__MODULE__)
unique_keys = Enum.map(Ash.Resource.Info.identities(__MODULE__), & &1.keys) ++ [pkey]
case Enum.find(unique_keys, fn unique_key ->
has_duplicates?(term, &Map.take(&1, unique_key))
end) do
nil ->
query =
__MODULE__
|> Ash.Query.put_context(:data, term)
|> Ash.Query.load(constraints[:load] || [])
query =
if constraints[:sort] do
Ash.Query.sort(query, constraints[:sort])
else
query
end
ShadowApi.read(query)
keys ->
{:error, message: "items must be unique on keys %{keys}", keys: Enum.join(keys, ",")}
end
end
defp has_duplicates?(list, func) do
list
|> Enum.reduce_while(MapSet.new(), fn x, acc ->
x = func.(x)
if MapSet.member?(acc, x) do
{:halt, 0}
else
{:cont, MapSet.put(acc, x)}
end
end)
|> is_integer()
end
def handle_change_array(nil, new_values, constraints) do
handle_change_array([], new_values, constraints)
end
def handle_change_array(old_values, nil, constraints) do
handle_change_array(old_values, [], constraints)
end
def handle_change_array(old_values, new_values, constraints) do
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
destroy_action =
constraints[:destroy_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :destroy).name
old_values
|> Enum.with_index()
|> Enum.reject(fn {old_value, _} ->
pkey = Map.take(old_value, pkey_fields)
Enum.any?(new_values, fn new_value ->
Map.take(new_value, pkey_fields) == pkey
end)
end)
|> Enum.reduce_while(:ok, fn {record, index}, :ok ->
case ShadowApi.destroy(record, action: destroy_action) do
:ok ->
{:cont, :ok}
{:error, error} ->
errors =
error
|> Ash.EmbeddableType.handle_errors()
|> Enum.map(fn keyword ->
Keyword.put(keyword, :index, index)
end)
{:halt, {:error, errors}}
end
end)
|> case do
:ok ->
{:ok, new_values}
{:error, error} ->
{:error, error}
end
end
def prepare_change_array(old_values, new_uncasted_values, constraints) do
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
if Enum.all?(pkey_fields, fn pkey_field ->
Ash.Resource.Info.attribute(__MODULE__, pkey_field).private?
end) do
{:ok, new_uncasted_values}
else
pkey_attributes =
Enum.into(pkey_fields, %{}, fn field ->
{field, Ash.Resource.Info.attribute(__MODULE__, field)}
end)
action =
constraints[:update_action] ||
Ash.Resource.Info.primary_action!(__MODULE__, :update).name
new_uncasted_values
|> Enum.with_index()
|> Enum.reduce_while({:ok, []}, fn {new, index}, {:ok, new_uncasted_values} ->
pkey =
Enum.into(pkey_fields, %{}, fn pkey_field ->
case fetch_key(new, pkey_field) do
:error ->
:error
value ->
attr = Map.get(pkey_attributes, pkey_field)
case Ash.Type.cast_input(attr.type, value, attr.constraints) do
{:ok, casted} ->
{pkey_field, casted}
_ ->
:error
end
end
end)
if Enum.any?(Map.values(pkey), &(&1 == :error)) do
{:cont, {:ok, [new | new_uncasted_values]}}
else
value_updating_from =
Enum.find(old_values, fn old_value ->
Map.take(old_value, pkey_fields) == pkey
end)
if value_updating_from do
value_updating_from
|> Ash.Changeset.for_update(action, new)
|> ShadowApi.update()
|> case do
{:ok, value} ->
{:cont, {:ok, [value | new_uncasted_values]}}
{:error, error} ->
errors =
error
|> Ash.EmbeddableType.handle_errors()
|> Enum.map(fn keyword ->
Keyword.put(keyword, :index, index)
end)
{:halt, {:error, errors}}
end
else
{:cont, {:ok, [new | new_uncasted_values]}}
end
end
end)
|> case do
{:ok, values} -> {:ok, Enum.reverse(values)}
{:error, error} -> {:error, error}
end
end
end
end
end
defmacro define_embeddable_type do
quote do
use Ash.Type
parent = __MODULE__
defmodule ShadowApi do
@moduledoc false
use Ash.Api
@parent parent
resources do
resource @parent, warn_on_compile_failure?: false
end
end
Ash.EmbeddableType.single_embed_implementation()
Ash.EmbeddableType.array_embed_implementation()
end
end
end