diff --git a/lib/ash_phoenix.ex b/lib/ash_phoenix.ex index 8dbda40..661b8f7 100644 --- a/lib/ash_phoenix.ex +++ b/lib/ash_phoenix.ex @@ -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, diff --git a/lib/ash_phoenix/form_data/changeset.ex b/lib/ash_phoenix/form_data/changeset.ex index d42392d..7f560a5 100644 --- a/lib/ash_phoenix/form_data/changeset.ex +++ b/lib/ash_phoenix/form_data/changeset.ex @@ -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 = diff --git a/lib/ash_phoenix/form_data/helpers.ex b/lib/ash_phoenix/form_data/helpers.ex index d8165e5..f5176d2 100644 --- a/lib/ash_phoenix/form_data/helpers.ex +++ b/lib/ash_phoenix/form_data/helpers.ex @@ -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 ->