diff --git a/lib/ash_phoenix.ex b/lib/ash_phoenix.ex index 75d0a1f..7bcdef6 100644 --- a/lib/ash_phoenix.ex +++ b/lib/ash_phoenix.ex @@ -241,22 +241,15 @@ defmodule AshPhoenix do [^outer_form_name, key | path] = decode_path(path) {argument, argument_manages} = - if changeset.action do - {nil, nil} + with action when not is_nil(action) <- changeset.action, + argument when not is_nil(argument) <- + Enum.find(changeset.action.arguments, &(to_string(&1.name) == key)), + manage_change when not is_nil(manage_change) <- + find_manage_change(argument, changeset.action) do + {argument, manage_change} else - # This is some magic to avoid having to pass in the relationship name - # when we can figure it out from the action - argument = - changeset.action.arguments - |> Enum.find(&(to_string(&1.name) == key)) - - if argument do - manage_change = find_manage_change(argument, changeset.action) - - if manage_change do - {argument, manage_change} - end - end + _ -> + {nil, nil} end changeset.resource @@ -350,22 +343,15 @@ defmodule AshPhoenix do [^outer_form_name, key | path] = decode_path(path) {argument, argument_manages} = - if changeset.action do - {nil, nil} + with action when not is_nil(action) <- changeset.action, + argument when not is_nil(argument) <- + Enum.find(changeset.action.arguments, &(to_string(&1.name) == key)), + manage_change when not is_nil(manage_change) <- + find_manage_change(argument, changeset.action) do + {argument, manage_change} else - # This is some magic to avoid having to pass in the relationship name - # when we can figure it out from the action - argument = - changeset.action.arguments - |> Enum.find(&(to_string(&1.name) == key)) - - if argument do - manage_change = find_manage_change(argument, changeset.action) - - if manage_change do - {argument, manage_change} - end - end + _ -> + {nil, nil} end changeset.resource @@ -455,6 +441,8 @@ defmodule AshPhoenix do {Map.update!(changeset.data, rel, &hide/1), nil} end end + else + {changeset.data, []} end changeset = mark_removed(changeset, new_value, rel) @@ -470,20 +458,15 @@ defmodule AshPhoenix do end defp find_manage_change(argument, action) do - Enum.find(action.changes, fn - {Ash.Resource.Change.ManageRelationship, opts} -> - opts[:argument] == argument.name + Enum.find_value(action.changes, fn + %{change: {Ash.Resource.Change.ManageRelationship, opts}} -> + if opts[:argument] == argument.name do + opts[:relationship] + end _ -> - false - end) - |> case do - nil -> nil - - {_, opts} -> - opts[:relationship_name] - end + end) end @doc """ @@ -670,13 +653,21 @@ defmodule AshPhoenix do end defp add_to_path(nil, [], add) do - add + List.wrap(add) end defp add_to_path(value, [], add) when is_list(value) do value ++ List.wrap(add) end + defp add_to_path(value, [], add) when value == %{} do + [value] ++ List.wrap(add) + end + + defp add_to_path(value, [key | rest], add) when is_integer(key) and value == %{} do + add_to_path([value], [key | rest], add) + end + defp 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 @@ -706,6 +697,8 @@ defmodule AshPhoenix do %{key => add_to_path(nil, rest, add)} end + defp add_to_path(_, _, add), do: add + defp remove_from_path(value, [key]) when is_integer(key) and is_list(value) do List.delete_at(value, key) end @@ -754,7 +747,15 @@ defmodule AshPhoenix do defp remove_from_path(value, _), do: value - defp decode_path(path) do + @doc """ + A utility for decoding the path of a form into a list. + + For example: + change[posts][0][comments][1] + ["change", "posts", 0, "comments", 1] + + """ + def decode_path(path) do path = Plug.Conn.Query.decode(path) do_decode_path(path) end diff --git a/lib/ash_phoenix/form_data/changeset.ex b/lib/ash_phoenix/form_data/changeset.ex index 346e43c..10fe045 100644 --- a/lib/ash_phoenix/form_data/changeset.ex +++ b/lib/ash_phoenix/form_data/changeset.ex @@ -95,6 +95,13 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do rel = Ash.Resource.Info.relationship(changeset.resource, field) -> data = relationship_data(changeset, rel, use_data?, opts[:id] || rel.name) + data = + if rel.cardinality == :many && data do + List.wrap(data) + else + data + end + {rel, rel.destination, data} attr = Ash.Resource.Info.attribute(changeset.resource, field) -> @@ -171,11 +178,25 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do [] -> nil - data when is_list(data) -> - List.last(data) - value -> - 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 + Ash.Changeset.new(data, value) + else + value + end + else + value + end end end end @@ -190,10 +211,29 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do end {manage, _opts} -> - manage + 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, manage) |> 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] || [] diff --git a/lib/ash_phoenix/form_data/helpers.ex b/lib/ash_phoenix/form_data/helpers.ex index fa5a8cc..66ea51d 100644 --- a/lib/ash_phoenix/form_data/helpers.ex +++ b/lib/ash_phoenix/form_data/helpers.ex @@ -61,7 +61,17 @@ defmodule AshPhoenix.FormData.Helpers do update_action = Ash.Resource.Info.primary_action(resource, :update) if update_action do - Ash.Changeset.for_update(data, update_action.name, %{}) + accepted_relationships = + resource + |> accepted_relationships(update_action) + |> Enum.map(fn relationship -> + {relationship.name, {:manage, [meta: [id: relationship.name]]}} + end) + + Ash.Changeset.for_update(data, update_action.name, %{}, + relationships: accepted_relationships, + actor: opts[:actor] + ) else Ash.Changeset.new(data) end @@ -69,7 +79,17 @@ defmodule AshPhoenix.FormData.Helpers do create_action = Ash.Resource.Info.primary_action(resource, :create) if create_action do - Ash.Changeset.for_create(resource, create_action.name, data) + accepted_relationships = + resource + |> accepted_relationships(create_action) + |> Enum.map(fn relationship -> + {relationship.name, {:manage, [meta: [id: relationship.name]]}} + end) + + Ash.Changeset.for_create(resource, create_action.name, data, + relationships: accepted_relationships, + actor: opts[:actor] + ) else Ash.Changeset.new(resource, data) end @@ -128,7 +148,17 @@ defmodule AshPhoenix.FormData.Helpers do cond do is_struct(data) -> if update_action do - Ash.Changeset.for_update(data, update_action.name, %{}) + accepted_relationships = + resource + |> accepted_relationships(update_action) + |> Enum.map(fn relationship -> + {relationship.name, {:manage, [meta: [id: relationship.name]]}} + end) + + Ash.Changeset.for_update(data, update_action.name, %{}, + relationships: accepted_relationships, + actor: opts[:actor] + ) else data |> Ash.Changeset.new() @@ -140,7 +170,17 @@ defmodule AshPhoenix.FormData.Helpers do true -> if create_action do - Ash.Changeset.for_create(resource, create_action.name, data) + accepted_relationships = + resource + |> accepted_relationships(create_action) + |> Enum.map(fn relationship -> + {relationship.name, {:manage, [meta: [id: relationship.name]]}} + end) + + Ash.Changeset.for_create(resource, create_action.name, data, + relationships: accepted_relationships, + actor: opts[:actor] + ) else resource |> Ash.Changeset.new(data) @@ -202,9 +242,9 @@ defmodule AshPhoenix.FormData.Helpers do data |> Enum.map(fn data -> if is_struct(data) do - Ash.Changeset.for_update(data, update_action, %{}) + Ash.Changeset.for_update(data, update_action, %{}, actor: opts[:actor]) else - Ash.Changeset.for_create(resource, create_action, data) + Ash.Changeset.for_create(resource, create_action, data, actor: opts[:actor]) end end) @@ -263,13 +303,13 @@ defmodule AshPhoenix.FormData.Helpers do changeset = cond do is_struct(data) -> - Ash.Changeset.for_update(data, update_action, %{}) + Ash.Changeset.for_update(data, update_action, %{}, actor: opts[:actor]) is_nil(data) -> nil true -> - Ash.Changeset.for_create(resource, create_action, data) + Ash.Changeset.for_create(resource, create_action, data, actor: opts[:actor]) end if changeset do @@ -303,4 +343,23 @@ defmodule AshPhoenix.FormData.Helpers do } end end + + defp accepted_relationships(resource, action) do + accepted = + if action.accept do + resource + |> Ash.Resource.Info.relationships() + |> Enum.filter(&(&1.name in action.accept)) + else + Ash.Resource.Info.relationships(resource) + end + + if action.reject do + resource + |> Ash.Resource.Info.relationships() + |> Enum.reject(&(&1.name in action.reject)) + else + accepted + end + end end