improvement: honor _union_type when applying input

This commit is contained in:
Zach Daniel 2024-06-06 13:58:46 -04:00
parent 2a74d91618
commit 3ae44d7cad
4 changed files with 145 additions and 121 deletions

View file

@ -152,7 +152,7 @@ defmodule AshPhoenix.Form.Auto do
updater =
fn opts, data, params ->
{type, constraints, tag, tag_value} =
{type_name, type, constraints, tag, tag_value} =
determine_type(constraints, data, params)
{embed, constraints, fake_embedded?} =
@ -177,14 +177,7 @@ defmodule AshPhoenix.Form.Auto do
transform_params =
if fake_embedded? do
fn form, params, type ->
if type == :nested do
AshPhoenix.Form.value(form, :value)
else
params
end
|> set_tag_value(tag, tag_value)
end
union_param_transformer(type_name)
else
fn _form, params, _type ->
set_tag_value(params, tag, tag_value)
@ -261,14 +254,14 @@ defmodule AshPhoenix.Form.Auto do
#{inspect(constraints[:types], pretty: true)}
"""
{_key, config} ->
{config[:type], config[:constraints], config[:tag], config[:tag_value]}
{key, config} ->
{key, config[:type], config[:constraints], config[:tag], config[:tag_value]}
end
end
defp determine_type(constraints, %Ash.Union{type: type}, _params) do
config = constraints[:types][type]
{config[:type], config[:constraints], config[:tag], config[:tag_value]}
{type, config[:type], config[:constraints], config[:tag], config[:tag_value]}
end
defp determine_type(constraints, %AshPhoenix.Form.WrappedValue{value: nil}, params)
@ -277,8 +270,8 @@ defmodule AshPhoenix.Form.Auto do
end
defp determine_type(constraints, nil, params) when params == %{} do
{_key, config} = Enum.at(constraints[:types], 0)
{config[:type], config[:constraints], config[:tag], config[:tag_value]}
{key, config} = Enum.at(constraints[:types], 0)
{key, config[:type], config[:constraints], config[:tag], config[:tag_value]}
end
defp determine_type(constraints, data, params) do
@ -303,8 +296,19 @@ defmodule AshPhoenix.Form.Auto do
#{inspect(constraints[:types], pretty: true)}
"""
{_key, config} ->
{config[:type], config[:constraints], config[:tag], config[:tag_value]}
{key, config} ->
{key, config[:type], config[:constraints], config[:tag], config[:tag_value]}
end
end
@doc false
def union_param_transformer(type_name) do
fn form, params, validation_type ->
if validation_type == :nested do
%Ash.Union{type: type_name, value: AshPhoenix.Form.value(form, :value)}
else
params
end
end
end

View file

@ -371,6 +371,9 @@ defmodule AshPhoenix.Form do
def for_action(resource_or_data, action, opts \\ [])
def for_action(%Ash.Union{value: value, type: type}, action, opts) do
opts =
Keyword.put_new(opts, :transform_params, AshPhoenix.Form.Auto.union_param_transformer(type))
form = for_action(value, action, opts)
%{form | params: Map.put(form.params, "_union_type", to_string(type))}

View file

@ -99,111 +99,134 @@ defmodule AshPhoenix.AutoFormTest do
end
test "simple unions" do
SimplePost
assert %Ash.Union{type: :predefined, value: :update} =
SimplePost
|> AshPhoenix.Form.for_create(:create,
domain: Domain,
forms: [
auto?: true
],
params: %{
"text" => "foobar"
}
)
|> AshPhoenix.Form.add_form(:union,
params: %{"_union_type" => "predefined", "value" => "update"}
)
|> AshPhoenix.Form.submit!()
|> Map.get(:union)
end
test "simple unions with invalid values" do
assert_raise Ash.Error.Invalid, ~r/atom must be one of "update", got: :create/, fn ->
SimplePost
|> AshPhoenix.Form.for_create(:create,
domain: Domain,
forms: [
auto?: true
],
params: %{
"text" => "foobar"
}
)
|> AshPhoenix.Form.add_form(:union,
params: %{"_union_type" => "predefined", "value" => "create"}
)
|> AshPhoenix.Form.submit!()
end
end
test "deeply nested unions" do
AshPhoenix.Test.DeepNestedUnionResource
|> AshPhoenix.Form.for_create(:create,
domain: Domain,
forms: [
auto?: true
],
]
)
|> AshPhoenix.Form.add_form(:items,
params: %{"subject" => %{"_union_type" => "predefined"}}
)
|> AshPhoenix.Form.submit!(
params: %{
"text" => "foobar"
"items" => %{
"0" => %{
"_form_type" => "create",
"_touched" => "_form_type,_persistent_id,_touched,subject",
"subject" => %{
"_form_type" => "create",
"_touched" => "_form_type,_persistent_id,_touched,_union_type,value",
"_union_type" => "predefined",
"value" => "update"
}
}
}
}
)
|> AshPhoenix.Form.add_form(:union, params: %{"type" => "custom"})
|> AshPhoenix.Form.submit!()
end
|> then(fn result ->
assert %Ash.Union{value: :update, type: :predefined} === Enum.at(result.items, 0).subject
end)
test "deeply nested unions" do
# AshPhoenix.Test.DeepNestedUnionResource
# |> AshPhoenix.Form.for_create(:create,
# domain: Domain,
# forms: [
# auto?: true
# ]
# )
# |> AshPhoenix.Form.add_form(:items,
# params: %{"subject" => %{"_union_type" => "predefined"}}
# )
# |> AshPhoenix.Form.submit!(
# params: %{
# "items" => %{
# "0" => %{
# "_form_type" => "create",
# "_touched" => "_form_type,_persistent_id,_touched,subject",
# "subject" => %{
# "_form_type" => "create",
# "_touched" => "_form_type,_persistent_id,_touched,_union_type,value",
# "_union_type" => "predefined",
# "value" => "update"
# }
# }
# }
# }
# )
# |> then(fn result ->
# assert %Ash.Union{value: :update, type: :predefined} === Enum.at(result.items, 0).subject
# end)
assert {:error, submitted_with_invalid} =
AshPhoenix.Test.DeepNestedUnionResource
|> AshPhoenix.Form.for_create(:create,
domain: Domain,
forms: [
auto?: true
]
)
|> AshPhoenix.Form.add_form(:items,
params: %{"subject" => %{"_union_type" => "predefined"}}
)
|> AshPhoenix.Form.submit(
params: %{
"items" => %{
"0" => %{
"_form_type" => "create",
"_touched" => "_form_type,_persistent_id,_touched,subject",
"subject" => %{
"_form_type" => "create",
"_touched" => "_form_type,_persistent_id,_touched,_union_type,value",
"_union_type" => "predefined",
"value" => "this_is_completely_unique"
}
}
}
}
)
# assert {:error, submitted_with_invalid} =
# AshPhoenix.Test.DeepNestedUnionResource
# |> AshPhoenix.Form.for_create(:create,
# domain: Domain,
# forms: [
# auto?: true
# ]
# )
# |> AshPhoenix.Form.add_form(:items,
# params: %{"subject" => %{"_union_type" => "predefined"}}
# )
# |> AshPhoenix.Form.submit(
# params: %{
# "items" => %{
# "0" => %{
# "_form_type" => "create",
# "_touched" => "_form_type,_persistent_id,_touched,subject",
# "subject" => %{
# "_form_type" => "create",
# "_touched" => "_form_type,_persistent_id,_touched,_union_type,value",
# "_union_type" => "predefined",
# "value" => "this_is_completely_unique"
# }
# }
# }
# }
# )
assert %{[:items, 0, :subject] => [value: "is invalid"]} =
AshPhoenix.Form.errors(submitted_with_invalid, for_path: :all)
# assert %{[:items, 0, :subject] => [value: "is invalid"]} =
# AshPhoenix.Form.errors(submitted_with_invalid, for_path: :all)
# AshPhoenix.Test.DeepNestedUnionResource
# |> AshPhoenix.Form.for_create(:create,
# domain: Domain,
# forms: [
# auto?: true
# ]
# )
# |> AshPhoenix.Form.add_form(:items,
# params: %{"subject" => %{"_union_type" => "predefined"}}
# )
# |> AshPhoenix.Form.submit!(
# params: %{
# "items" => %{
# "0" => %{
# "_form_type" => "create",
# "_touched" => "_form_type,_persistent_id,_touched,subject",
# "subject" => %{
# "_form_type" => "create",
# "_touched" => "_form_type,_persistent_id,_touched,_union_type,value",
# "_union_type" => "custom",
# "value" => "different"
# }
# }
# }
# }
# )
# |> then(fn result ->
# assert %Ash.Union{value: "different", type: :custom} === Enum.at(result.items, 0).subject
# end)
AshPhoenix.Test.DeepNestedUnionResource
|> AshPhoenix.Form.for_create(:create,
domain: Domain,
forms: [
auto?: true
]
)
|> AshPhoenix.Form.add_form(:items,
params: %{"subject" => %{"_union_type" => "predefined"}}
)
|> AshPhoenix.Form.submit!(
params: %{
"items" => %{
"0" => %{
"_form_type" => "create",
"_touched" => "_form_type,_persistent_id,_touched,subject",
"subject" => %{
"_form_type" => "create",
"_touched" => "_form_type,_persistent_id,_touched,_union_type,value",
"_union_type" => "custom",
"value" => "different"
}
}
}
}
)
|> then(fn result ->
assert %Ash.Union{value: "different", type: :custom} === Enum.at(result.items, 0).subject
end)
AshPhoenix.Test.DeepNestedUnionResource
|> AshPhoenix.Form.for_create(:create,

View file

@ -3,18 +3,12 @@ defmodule AshPhoenix.Test.SimplePost.SimpleUnion do
subtype_of: :union,
constraints: [
types: [
custom: [
type: :string
],
predefined: [
type: :atom,
constraints: [one_of: [:update]],
tag: :type,
tag_value: :predefined,
cast_tag?: true
],
custom: [
type: :string,
tag: :type,
tag_value: :custom,
cast_tag?: true
constraints: [one_of: [:update]]
]
]
]