improvement: various improvements to relationship manipulation functions

fix: add removed embeds to hidden fields
This commit is contained in:
Zach Daniel 2021-05-14 00:34:01 -04:00
parent 4eca3c8333
commit 98e26a9544
3 changed files with 215 additions and 172 deletions

View file

@ -241,39 +241,43 @@ defmodule AshPhoenix do
#{Ash.OptionsHelpers.docs(@add_value_opts)}
"""
@spec add_value(Ash.Changeset.t(), String.t(), String.t(), Keyword.t()) :: Ash.Changeset.t()
def add_value(changeset, path, outer_form_name, opts \\ []) do
def add_value(changeset, original_path, outer_form_name, opts \\ []) do
opts = Ash.OptionsHelpers.validate!(opts, @add_value_opts)
add = opts[:add]
[^outer_form_name, key | path] = decode_path(path)
[^outer_form_name, key | path] = decode_path(original_path)
attribute_or_argument =
Ash.Resource.Info.attribute(changeset.resource, key) ||
find_argument(changeset, key)
if match?({x, y} when not is_nil(x) and not is_nil(y), argument_and_manages(changeset, key)) do
add_related(changeset, original_path, outer_form_name, add: add)
else
attribute_or_argument =
Ash.Resource.Info.attribute(changeset.resource, key) ||
find_argument(changeset, key)
if attribute_or_argument do
value =
case attribute_or_argument do
%Ash.Resource.Actions.Argument{} = argument ->
changeset
|> Ash.Changeset.get_argument(argument.name)
attribute ->
changeset
|> Ash.Changeset.get_attribute(attribute.name)
end
new_value = add_to_path(value, path, add)
if attribute_or_argument do
value =
case attribute_or_argument do
%Ash.Resource.Actions.Argument{} = argument ->
changeset
|> Ash.Changeset.get_argument(argument.name)
Ash.Changeset.set_argument(changeset, argument.name, new_value)
attribute ->
changeset
|> Ash.Changeset.get_attribute(attribute.name)
Ash.Changeset.change_attribute(changeset, attribute.name, new_value)
end
new_value = add_to_path(value, path, add)
case attribute_or_argument do
%Ash.Resource.Actions.Argument{} = argument ->
Ash.Changeset.set_argument(changeset, argument.name, new_value)
attribute ->
Ash.Changeset.change_attribute(changeset, attribute.name, new_value)
else
changeset
end
else
changeset
end
end
@ -281,38 +285,43 @@ defmodule AshPhoenix do
A utility to support "remove" buttons on list attributes and arguments used in forms.
"""
@spec remove_value(Ash.Changeset.t(), String.t(), String.t()) :: Ash.Changeset.t()
def remove_value(changeset, path, outer_form_name) do
[^outer_form_name, key | path] = decode_path(path)
def remove_value(changeset, original_path, outer_form_name) do
[^outer_form_name, key | path] = decode_path(original_path)
attribute_or_argument =
Ash.Resource.Info.attribute(changeset.resource, key) ||
find_argument(changeset, key)
if attribute_or_argument do
value =
if match?({x, y} when not is_nil(x) and not is_nil(y), argument_and_manages(changeset, key)) do
{_, changeset} = remove_related(changeset, original_path, outer_form_name)
changeset
else
if attribute_or_argument do
value =
case attribute_or_argument do
%Ash.Resource.Actions.Argument{} = argument ->
changeset
|> Ash.Changeset.get_argument(argument.name)
|> List.wrap()
attribute ->
changeset
|> Ash.Changeset.get_attribute(attribute.name)
|> List.wrap()
end
new_value = remove_from_path(value, path)
case attribute_or_argument do
%Ash.Resource.Actions.Argument{} = argument ->
changeset
|> Ash.Changeset.get_argument(argument.name)
|> List.wrap()
Ash.Changeset.set_argument(changeset, argument.name, new_value)
attribute ->
changeset
|> Ash.Changeset.get_attribute(attribute.name)
|> List.wrap()
Ash.Changeset.change_attribute(changeset, attribute.name, new_value)
end
new_value = remove_from_path(value, path)
case attribute_or_argument do
%Ash.Resource.Actions.Argument{} = argument ->
Ash.Changeset.set_argument(changeset, argument.name, new_value)
attribute ->
Ash.Changeset.change_attribute(changeset, attribute.name, new_value)
else
changeset
end
else
changeset
end
end
@ -362,9 +371,14 @@ defmodule AshPhoenix do
@spec add_related(Ash.Changeset.t(), String.t(), String.t(), Keyword.t()) :: Ash.Changeset.t()
def add_related(changeset, path, outer_form_name, opts \\ []) do
opts = Ash.OptionsHelpers.validate!(opts, @add_related_opts)
add = opts[:add] || %{}
add = Keyword.get(opts, :add, %{})
[^outer_form_name, key | path] = decode_path(path)
[^outer_form_name, key | path] =
if is_list(path) do
path
else
decode_path(path)
end
{argument, argument_manages} = argument_and_manages(changeset, key)
@ -534,14 +548,17 @@ defmodule AshPhoenix do
Enum.map(related, &hide/1)
end), []}
match?([i] when is_integer(i), path) and is_list(value) ->
[i] = path
match?([i | _] when is_integer(i), path) and is_list(value) ->
[i | _] = path
new_value = hide_at_not_hidden(Map.get(changeset.data, rel), i)
{Map.put(changeset.data, rel, new_value), new_value}
path == [] || match?([i] when is_integer(i), path) ->
{Map.update!(changeset.data, rel, &hide/1), nil}
true ->
{changeset.data, new_value}
end
end
else
@ -770,23 +787,23 @@ defmodule AshPhoenix do
|> Enum.all?(&hidden?/1)
end
defp add_to_path(nil, [], nil) do
def add_to_path(nil, [], nil) do
[nil]
end
defp add_to_path(nil, [], add) do
def add_to_path(nil, [], add) do
add
end
defp add_to_path(value, [], nil) when is_list(value) do
def add_to_path(value, [], nil) when is_list(value) do
value ++ [nil]
end
defp add_to_path(value, [], add) when is_list(value) do
def add_to_path(value, [], add) when is_list(value) do
value ++ List.wrap(add)
end
defp add_to_path(value, [], add) when is_map(value) do
def add_to_path(value, [], add) when is_map(value) do
case last_index(value) do
:error ->
%{"0" => value, "1" => add}
@ -796,16 +813,16 @@ defmodule AshPhoenix do
end
end
defp add_to_path(value, [key | rest], add) when is_integer(key) and is_list(value) do
def add_to_path(value, [key | rest], add) when is_integer(key) and is_list(value) do
List.update_at(value, key, &add_to_path(&1, rest, add))
end
defp add_to_path(empty, [key | rest], add) when is_integer(key) and empty in [nil, []] do
def add_to_path(empty, [key | rest], add) when is_integer(key) and empty in [nil, []] do
[add_to_path(nil, rest, add)]
end
defp add_to_path(value, [key | rest], add)
when (is_binary(key) or is_atom(key)) and is_map(value) do
def add_to_path(value, [key | rest], add)
when (is_binary(key) or is_atom(key)) and is_map(value) do
cond do
Map.has_key?(value, key) ->
Map.update!(value, key, &add_to_path(&1, rest, add))
@ -821,15 +838,15 @@ defmodule AshPhoenix do
end
end
defp add_to_path(nil, [key | rest], add) when is_binary(key) or is_atom(key) do
def add_to_path(nil, [key | rest], add) when is_binary(key) or is_atom(key) do
%{key => add_to_path(nil, rest, add)}
end
defp add_to_path([item], [key | _] = path, add) when is_binary(key) do
def add_to_path([item], [key | _] = path, add) when is_binary(key) do
[add_to_path(item, path, add)]
end
defp add_to_path(_, _, add), do: add
def add_to_path(_, _, add), do: add
defp last_index(map) do
{:ok,

View file

@ -50,7 +50,7 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
id = Keyword.get(opts, :id) || name
hidden =
if changeset.action_type in [:update] do
if changeset.action_type in [:update, :destroy] do
changeset.data
|> Map.take(Ash.Resource.Info.primary_key(changeset.resource))
|> Enum.to_list()
@ -58,6 +58,20 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
[]
end
hidden =
changeset.resource
|> Ash.Resource.Info.attributes()
|> Enum.filter(&Ash.Type.embedded_type?(&1.type))
|> Enum.reduce(hidden, fn attribute, hidden ->
case Ash.Changeset.fetch_change(changeset, attribute.name) do
{:ok, empty} when empty in [nil, []] ->
Keyword.put(hidden, attribute.name, nil)
_ ->
hidden
end
end)
removed_embed_values =
changeset.context[:private][:removed_keys]
|> Kernel.||(%{})
@ -196,110 +210,6 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
defp unwrap([value | _]), do: value
defp unwrap(value), do: value
defp relationship_data(changeset, %{cardinality: :one} = rel, use_data?, id) do
case get_managed(changeset, rel.name, id) do
nil ->
if use_data? do
changeset_data(changeset, rel)
else
nil
end
{manage, _opts} ->
case manage do
[] ->
nil
value ->
value =
if is_list(value) do
List.last(value)
else
value
end
if use_data? do
data = changeset_data(changeset, rel)
if data do
data
|> Ash.Changeset.new(take_attributes(value, rel.destination))
|> Map.put(:params, value)
else
value
end
else
value
end
end
end
end
defp relationship_data(changeset, rel, use_data?, id) do
case get_managed(changeset, rel.name, id) do
nil ->
if use_data? do
changeset_data(changeset, rel)
else
[]
end
{manage, _opts} ->
if use_data? do
changeset
|> changeset_data(rel)
|> zip_changes(manage)
else
manage
end
end
end
defp zip_changes([], manage) do
manage
end
defp zip_changes([record | rest_data], [manage | rest_manage]) do
[
Ash.Changeset.new(record, take_attributes(manage, record.__struct__))
|> Map.put(:params, manage)
] ++
zip_changes(rest_data, rest_manage)
end
defp zip_changes(records, []) do
records
end
defp get_managed(changeset, relationship_name, id) do
manage = changeset.relationships[relationship_name] || []
Enum.find(manage, fn {_, opts} -> opts[:meta][:id] == id end)
end
defp changeset_data(changeset, rel) do
data = Map.get(changeset.data, rel.name)
case data do
%Ash.NotLoaded{} ->
default_data(rel)
data ->
if is_list(data) do
Enum.reject(data, &hidden?/1)
else
if hidden?(data) do
nil
else
data
end
end
end
end
defp default_data(%{cardinality: :many}), do: []
defp default_data(%{cardinality: :one}), do: nil
@impl true
def input_validations(changeset, _, field) do
attribute_or_argument =

View file

@ -68,6 +68,113 @@ defmodule AshPhoenix.FormData.Helpers do
def get_embedded(_), do: nil
def relationship_data(changeset, %{cardinality: :one} = rel, use_data?, id) do
case get_managed(changeset, rel.name, id) do
nil ->
if use_data? do
changeset_data(changeset, rel)
else
nil
end
{manage, _opts} ->
case manage do
nil ->
nil
[] ->
nil
value ->
value =
if is_list(value) do
List.last(value)
else
value
end
if use_data? do
data = changeset_data(changeset, rel)
if data do
data
|> Ash.Changeset.new()
|> Map.put(:params, value)
else
value
end
else
value
end
end
end
end
def relationship_data(changeset, rel, use_data?, id) do
case get_managed(changeset, rel.name, id) do
nil ->
if use_data? do
changeset_data(changeset, rel)
else
[]
end
{manage, _opts} ->
if use_data? do
changeset
|> changeset_data(rel)
|> zip_changes(manage)
else
manage
end
end
end
defp zip_changes([], manage) do
manage
end
defp zip_changes([record | rest_data], [manage | rest_manage]) do
[
Ash.Changeset.new(record, %{})
|> Map.put(:params, manage)
] ++
zip_changes(rest_data, rest_manage)
end
defp zip_changes(records, []) do
records
end
defp changeset_data(changeset, rel) do
data = Map.get(changeset.data, rel.name)
case data do
%Ash.NotLoaded{} ->
default_data(rel)
data ->
if is_list(data) do
Enum.reject(data, &hidden?/1)
else
if hidden?(data) do
nil
else
data
end
end
end
end
defp default_data(%{cardinality: :many}), do: []
defp default_data(%{cardinality: :one}), do: nil
defp get_managed(changeset, relationship_name, id) do
manage = changeset.relationships[relationship_name] || []
Enum.find(manage, fn {_, opts} -> opts[:meta][:id] == id end)
end
@doc false
def to_nested_form(
data,
@ -156,7 +263,7 @@ defmodule AshPhoenix.FormData.Helpers do
id: id,
name: name,
errors: form_for_errors(changeset, opts),
data: data,
data: changeset.data,
params: changeset.params,
hidden: hidden,
options: opts
@ -183,9 +290,9 @@ defmodule AshPhoenix.FormData.Helpers do
data
|> Enum.map(fn data ->
if is_struct(data) do
Ash.Changeset.for_update(data, update_action, %{}, actor: opts[:actor])
Ash.Changeset.for_update(data, update_action, params(data), actor: opts[:actor])
else
Ash.Changeset.for_create(resource, create_action, data, actor: opts[:actor])
Ash.Changeset.for_create(resource, create_action, params(data), actor: opts[:actor])
end
end)
@ -241,13 +348,13 @@ defmodule AshPhoenix.FormData.Helpers do
changeset =
cond do
is_struct(data) ->
Ash.Changeset.for_update(data, update_action, %{}, actor: opts[:actor])
Ash.Changeset.for_update(data, update_action, params(data), actor: opts[:actor])
is_nil(data) ->
nil
true ->
Ash.Changeset.for_create(resource, create_action, data, actor: opts[:actor])
Ash.Changeset.for_create(resource, create_action, params(data), actor: opts[:actor])
end
if changeset do
@ -273,7 +380,7 @@ defmodule AshPhoenix.FormData.Helpers do
id: id,
name: name,
errors: form_for_errors(changeset, opts),
data: data,
data: changeset.data,
params: changeset.params,
hidden: hidden,
options: opts
@ -303,9 +410,15 @@ defmodule AshPhoenix.FormData.Helpers do
update_action = action!(resource, :update, opts[:update_action])
data
|> Ash.Changeset.new()
|> case do
%Ash.Changeset{} = changeset ->
changeset
other ->
Ash.Changeset.new(other)
end
|> set_source_context({relationship, source_changeset})
|> Ash.Changeset.for_update(update_action.name, %{}, actor: opts[:actor])
|> Ash.Changeset.for_update(update_action.name, params(data), actor: opts[:actor])
end
else
if opts[:create_action] == :_raw do
@ -324,6 +437,9 @@ defmodule AshPhoenix.FormData.Helpers do
end
end
defp params(%Ash.Changeset{params: params}), do: params
defp params(_), do: nil
defp set_source_context(changeset, {relationship, original_changeset}) do
case original_changeset.context[:manage_relationship_source] do
nil ->