mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
improvement: bulk destroy for handling array changes in embeds
improvement: set `__union_tag__` constraint in array handlers for unions fix: sleep to avoid uuidv7 specifity flaky test test: remove unused variable in tests
This commit is contained in:
parent
ec4ff55529
commit
83434b0d1a
4 changed files with 179 additions and 175 deletions
|
@ -731,19 +731,14 @@ defmodule Ash.EmbeddableType do
|
||||||
def handle_change_array(old_values, new_values, constraints) do
|
def handle_change_array(old_values, new_values, constraints) do
|
||||||
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
|
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
|
||||||
|
|
||||||
destroy_action =
|
|
||||||
constraints[:destroy_action] ||
|
|
||||||
Ash.Resource.Info.primary_action!(__MODULE__, :destroy).name
|
|
||||||
|
|
||||||
old_values
|
old_values
|
||||||
|> List.wrap()
|
|> List.wrap()
|
||||||
|> Enum.with_index()
|
|
||||||
|> then(fn list ->
|
|> then(fn list ->
|
||||||
if Enum.empty?(pkey_fields) do
|
if Enum.empty?(pkey_fields) do
|
||||||
list
|
list
|
||||||
else
|
else
|
||||||
list
|
list
|
||||||
|> Enum.reject(fn {old_value, _} ->
|
|> Enum.reject(fn old_value ->
|
||||||
pkey = Map.take(old_value, pkey_fields)
|
pkey = Map.take(old_value, pkey_fields)
|
||||||
|
|
||||||
Enum.any?(new_values, fn new_value ->
|
Enum.any?(new_values, fn new_value ->
|
||||||
|
@ -752,33 +747,45 @@ defmodule Ash.EmbeddableType do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.reduce_while(:ok, fn {record, index}, :ok ->
|
|
||||||
record
|
|
||||||
|> Ash.Changeset.new()
|
|
||||||
|> Ash.EmbeddableType.copy_source(constraints)
|
|
||||||
|> Ash.Changeset.for_destroy(destroy_action, %{}, domain: ShadowDomain)
|
|
||||||
|> Ash.destroy()
|
|
||||||
|> case do
|
|> case 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}
|
{:ok, new_values}
|
||||||
|
|
||||||
{:error, error} ->
|
to_destroy ->
|
||||||
{:error, error}
|
destroy_action =
|
||||||
|
constraints[:destroy_action] ||
|
||||||
|
Ash.Resource.Info.primary_action!(__MODULE__, :destroy).name
|
||||||
|
|
||||||
|
{context, opts} =
|
||||||
|
case constraints[:__source__] do
|
||||||
|
%Ash.Changeset{context: context} = source ->
|
||||||
|
{Map.put(context, :__source__, source),
|
||||||
|
Ash.Context.to_opts(context[:private] || %{})}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{%{}, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
case Ash.bulk_destroy(
|
||||||
|
to_destroy,
|
||||||
|
destroy_action,
|
||||||
|
%{},
|
||||||
|
Keyword.merge(opts,
|
||||||
|
domain: ShadowDomain,
|
||||||
|
context: context,
|
||||||
|
sorted?: true,
|
||||||
|
skip_unknown_inputs: skip_unknown_inputs(constraints),
|
||||||
|
return_records?: true,
|
||||||
|
return_errors?: true,
|
||||||
|
batch_size: 1_000_000_000
|
||||||
|
)
|
||||||
|
) do
|
||||||
|
%Ash.BulkResult{status: :success} ->
|
||||||
|
{:ok, new_values}
|
||||||
|
|
||||||
|
%Ash.BulkResult{errors: errors} ->
|
||||||
|
{:error, Ash.EmbeddableType.handle_errors(errors)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -390,39 +390,15 @@ defmodule Ash.Type.Union do
|
||||||
def cast_input(nil, _), do: {:ok, nil}
|
def cast_input(nil, _), do: {:ok, nil}
|
||||||
|
|
||||||
def cast_input(%Ash.Union{value: value, type: type_name}, constraints) do
|
def cast_input(%Ash.Union{value: value, type: type_name}, constraints) do
|
||||||
type = constraints[:types][type_name][:type]
|
case try_cast_type(value, constraints, type_name, constraints[:types][type_name], [], false) do
|
||||||
inner_constraints = constraints[:types][type_name][:constraints] || []
|
{_, {:ok, value}} ->
|
||||||
|
{:ok, value}
|
||||||
|
|
||||||
inner_constraints =
|
{_, {:expose_error, errors}} ->
|
||||||
if Ash.Type.embedded_type?(type) do
|
{:error, errors}
|
||||||
case constraints[:__source__] do
|
|
||||||
%Ash.Changeset{} = source ->
|
|
||||||
Keyword.put(inner_constraints, :__source__, source)
|
|
||||||
|
|
||||||
_ ->
|
{_, {:error, error}} ->
|
||||||
inner_constraints
|
{:error, error_message(error)}
|
||||||
end
|
|
||||||
|> Keyword.put(:__union_tag__, constraints[:types][type_name][:tag])
|
|
||||||
else
|
|
||||||
inner_constraints
|
|
||||||
end
|
|
||||||
|
|
||||||
case Ash.Type.cast_input(
|
|
||||||
type,
|
|
||||||
value,
|
|
||||||
inner_constraints
|
|
||||||
) do
|
|
||||||
{:ok, value} ->
|
|
||||||
case Ash.Type.apply_constraints(type, value, inner_constraints) do
|
|
||||||
{:ok, value} ->
|
|
||||||
{:ok, %Ash.Union{value: value, type: type_name}}
|
|
||||||
|
|
||||||
{:error, other} ->
|
|
||||||
{:error, other}
|
|
||||||
end
|
|
||||||
|
|
||||||
error ->
|
|
||||||
error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -431,6 +407,27 @@ defmodule Ash.Type.Union do
|
||||||
|
|
||||||
types
|
types
|
||||||
|> Enum.reduce_while({:error, []}, fn {type_name, config}, {:error, errors} ->
|
|> Enum.reduce_while({:error, []}, fn {type_name, config}, {:error, errors} ->
|
||||||
|
try_cast_type(value, constraints, type_name, config, errors)
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
{:error, errors} when is_binary(errors) ->
|
||||||
|
{:error, errors}
|
||||||
|
|
||||||
|
{:error, errors} ->
|
||||||
|
{:error, error_message(errors)}
|
||||||
|
|
||||||
|
{:expose_error, errors} ->
|
||||||
|
{:error, errors}
|
||||||
|
|
||||||
|
{:ok, value} ->
|
||||||
|
{:ok, value}
|
||||||
|
|
||||||
|
value ->
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp try_cast_type(value, constraints, type_name, config, errors, tags_must_match? \\ true) do
|
||||||
type = config[:type]
|
type = config[:type]
|
||||||
|
|
||||||
if is_map(value) && config[:tag] do
|
if is_map(value) && config[:tag] do
|
||||||
|
@ -457,11 +454,7 @@ defmodule Ash.Type.Union do
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
their_tag_value = get_tag(value, config[:tag])
|
if !tags_must_match? || tags_equal?(tag_value, get_tag(value, config[:tag])) do
|
||||||
|
|
||||||
tags_equal? = tags_equal?(tag_value, their_tag_value)
|
|
||||||
|
|
||||||
if tags_equal? do
|
|
||||||
value =
|
value =
|
||||||
if Keyword.get(config, :cast_tag?, true) do
|
if Keyword.get(config, :cast_tag?, true) do
|
||||||
value
|
value
|
||||||
|
@ -540,23 +533,6 @@ defmodule Ash.Type.Union do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
|> case do
|
|
||||||
{:error, errors} when is_binary(errors) ->
|
|
||||||
{:error, errors}
|
|
||||||
|
|
||||||
{:error, errors} ->
|
|
||||||
{:error, error_message(errors)}
|
|
||||||
|
|
||||||
{:expose_error, errors} ->
|
|
||||||
{:error, errors}
|
|
||||||
|
|
||||||
{:ok, value} ->
|
|
||||||
{:ok, value}
|
|
||||||
|
|
||||||
value ->
|
|
||||||
value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_tag(map, tag) do
|
defp get_tag(map, tag) do
|
||||||
|
@ -830,10 +806,18 @@ defmodule Ash.Type.Union do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
type = constraints[:types][name][:type]
|
type = constraints[:types][name][:type]
|
||||||
|
type_constraints = constraints[:types][name][:constraints] || []
|
||||||
|
|
||||||
|
type_constraints =
|
||||||
|
if union_tag = constraints[:types][name][:tag] do
|
||||||
|
Keyword.put(type_constraints, :__union_tag__, union_tag)
|
||||||
|
else
|
||||||
|
type_constraints
|
||||||
|
end
|
||||||
|
|
||||||
item_constraints =
|
item_constraints =
|
||||||
Ash.Type.include_source({:array, type}, constraints[:__source__],
|
Ash.Type.include_source({:array, type}, constraints[:__source__],
|
||||||
items: constraints[:types][name][:constraints] || []
|
items: type_constraints
|
||||||
)
|
)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
|
@ -931,13 +915,21 @@ defmodule Ash.Type.Union do
|
||||||
new_values = Enum.map(new_values, & &1.value)
|
new_values = Enum.map(new_values, & &1.value)
|
||||||
|
|
||||||
type = constraints[:types][name][:type]
|
type = constraints[:types][name][:type]
|
||||||
|
type_constraints = constraints[:types][name][:constraints] || []
|
||||||
|
|
||||||
|
type_constraints =
|
||||||
|
if union_tag = constraints[:types][name][:tag] do
|
||||||
|
Keyword.put(type_constraints, :__union_tag__, union_tag)
|
||||||
|
else
|
||||||
|
type_constraints
|
||||||
|
end
|
||||||
|
|
||||||
result =
|
result =
|
||||||
Ash.Type.handle_change(
|
Ash.Type.handle_change(
|
||||||
{:array, type},
|
{:array, type},
|
||||||
old_values_by_type[name] || [],
|
old_values_by_type[name] || [],
|
||||||
new_values,
|
new_values,
|
||||||
Keyword.put(constraints, :items, constraints[:types][name][:constraints])
|
Keyword.put(constraints, :items, type_constraints)
|
||||||
)
|
)
|
||||||
|
|
||||||
case result do
|
case result do
|
||||||
|
|
|
@ -283,7 +283,8 @@ defmodule Ash.Test.CodeInterfaceTest do
|
||||||
|
|
||||||
assert bob.first_name == "bob_updated"
|
assert bob.first_name == "bob_updated"
|
||||||
|
|
||||||
assert_received {:notification, %Ash.Notifier.Notification{} = notification}
|
assert_received {:notification,
|
||||||
|
%Ash.Notifier.Notification{resource: User, action: %{name: :update}}}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "bulk update can take a @context options" do
|
test "bulk update can take a @context options" do
|
||||||
|
|
|
@ -19,8 +19,12 @@ defmodule Ash.Test.UUIDv7Test do
|
||||||
|
|
||||||
test "bingenerate/1 is ordered" do
|
test "bingenerate/1 is ordered" do
|
||||||
uuids =
|
uuids =
|
||||||
for _ <- 1..10_000 do
|
for _ <- 1..100 do
|
||||||
Ash.UUIDv7.bingenerate()
|
uuid = Ash.UUIDv7.bingenerate()
|
||||||
|
# only guaranteed sorted if >= 1 nanosecond apart
|
||||||
|
# can't sleep for one nanoseond AFAIK, so sleep for 1 ms
|
||||||
|
:timer.sleep(1)
|
||||||
|
uuid
|
||||||
end
|
end
|
||||||
|
|
||||||
assert uuids == Enum.sort(uuids)
|
assert uuids == Enum.sort(uuids)
|
||||||
|
|
Loading…
Reference in a new issue