mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 13:03:02 +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
|
||||
pkey_fields = Ash.Resource.Info.primary_key(__MODULE__)
|
||||
|
||||
destroy_action =
|
||||
constraints[:destroy_action] ||
|
||||
Ash.Resource.Info.primary_action!(__MODULE__, :destroy).name
|
||||
|
||||
old_values
|
||||
|> List.wrap()
|
||||
|> Enum.with_index()
|
||||
|> then(fn list ->
|
||||
if Enum.empty?(pkey_fields) do
|
||||
list
|
||||
else
|
||||
list
|
||||
|> Enum.reject(fn {old_value, _} ->
|
||||
|> Enum.reject(fn old_value ->
|
||||
pkey = Map.take(old_value, pkey_fields)
|
||||
|
||||
Enum.any?(new_values, fn new_value ->
|
||||
|
@ -752,33 +747,45 @@ defmodule Ash.EmbeddableType do
|
|||
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
|
||||
: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}
|
||||
to_destroy ->
|
||||
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
|
||||
|
||||
|
|
|
@ -390,39 +390,15 @@ defmodule Ash.Type.Union do
|
|||
def cast_input(nil, _), do: {:ok, nil}
|
||||
|
||||
def cast_input(%Ash.Union{value: value, type: type_name}, constraints) do
|
||||
type = constraints[:types][type_name][:type]
|
||||
inner_constraints = constraints[:types][type_name][:constraints] || []
|
||||
case try_cast_type(value, constraints, type_name, constraints[:types][type_name], [], false) do
|
||||
{_, {:ok, value}} ->
|
||||
{:ok, value}
|
||||
|
||||
inner_constraints =
|
||||
if Ash.Type.embedded_type?(type) do
|
||||
case constraints[:__source__] do
|
||||
%Ash.Changeset{} = source ->
|
||||
Keyword.put(inner_constraints, :__source__, source)
|
||||
{_, {:expose_error, errors}} ->
|
||||
{:error, errors}
|
||||
|
||||
_ ->
|
||||
inner_constraints
|
||||
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
|
||||
{_, {:error, error}} ->
|
||||
{:error, error_message(error)}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -431,6 +407,27 @@ defmodule Ash.Type.Union do
|
|||
|
||||
types
|
||||
|> 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]
|
||||
|
||||
if is_map(value) && config[:tag] do
|
||||
|
@ -457,11 +454,7 @@ defmodule Ash.Type.Union do
|
|||
value
|
||||
end
|
||||
|
||||
their_tag_value = get_tag(value, config[:tag])
|
||||
|
||||
tags_equal? = tags_equal?(tag_value, their_tag_value)
|
||||
|
||||
if tags_equal? do
|
||||
if !tags_must_match? || tags_equal?(tag_value, get_tag(value, config[:tag])) do
|
||||
value =
|
||||
if Keyword.get(config, :cast_tag?, true) do
|
||||
value
|
||||
|
@ -540,23 +533,6 @@ defmodule Ash.Type.Union do
|
|||
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
|
||||
|
||||
defp get_tag(map, tag) do
|
||||
|
@ -830,10 +806,18 @@ defmodule Ash.Type.Union do
|
|||
end)
|
||||
|
||||
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 =
|
||||
Ash.Type.include_source({:array, type}, constraints[:__source__],
|
||||
items: constraints[:types][name][:constraints] || []
|
||||
items: type_constraints
|
||||
)
|
||||
|
||||
result =
|
||||
|
@ -931,13 +915,21 @@ defmodule Ash.Type.Union do
|
|||
new_values = Enum.map(new_values, & &1.value)
|
||||
|
||||
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 =
|
||||
Ash.Type.handle_change(
|
||||
{:array, type},
|
||||
old_values_by_type[name] || [],
|
||||
new_values,
|
||||
Keyword.put(constraints, :items, constraints[:types][name][:constraints])
|
||||
Keyword.put(constraints, :items, type_constraints)
|
||||
)
|
||||
|
||||
case result do
|
||||
|
|
|
@ -283,7 +283,8 @@ defmodule Ash.Test.CodeInterfaceTest do
|
|||
|
||||
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
|
||||
|
||||
test "bulk update can take a @context options" do
|
||||
|
|
|
@ -19,8 +19,12 @@ defmodule Ash.Test.UUIDv7Test do
|
|||
|
||||
test "bingenerate/1 is ordered" do
|
||||
uuids =
|
||||
for _ <- 1..10_000 do
|
||||
Ash.UUIDv7.bingenerate()
|
||||
for _ <- 1..100 do
|
||||
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
|
||||
|
||||
assert uuids == Enum.sort(uuids)
|
||||
|
|
Loading…
Reference in a new issue