mirror of
https://github.com/ash-project/ash_phoenix.git
synced 2024-09-20 07:12:49 +12:00
fix: synthetically cast attributes in read forms
fix: raise explicitly on non-existent action
This commit is contained in:
parent
728df80432
commit
4c013819d0
3 changed files with 77 additions and 3 deletions
|
@ -81,6 +81,8 @@ defmodule AshPhoenix.Form.Auto do
|
||||||
end
|
end
|
||||||
|
|
||||||
def related(resource, action, auto_opts) do
|
def related(resource, action, auto_opts) do
|
||||||
|
passed_in_action = action
|
||||||
|
|
||||||
action =
|
action =
|
||||||
if is_atom(action) do
|
if is_atom(action) do
|
||||||
Ash.Resource.Info.action(resource, action)
|
Ash.Resource.Info.action(resource, action)
|
||||||
|
@ -88,6 +90,10 @@ defmodule AshPhoenix.Form.Auto do
|
||||||
action
|
action
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if is_nil(action) && is_atom(passed_in_action) do
|
||||||
|
raise "No such action :#{passed_in_action} for #{inspect(resource)}"
|
||||||
|
end
|
||||||
|
|
||||||
action.arguments
|
action.arguments
|
||||||
|> Enum.reject(& &1.private?)
|
|> Enum.reject(& &1.private?)
|
||||||
|> Enum.filter(&(&1.type in [{:array, :map}, :map, Ash.Type.Map, {:array, Ash.Type.Map}]))
|
|> Enum.filter(&(&1.type in [{:array, :map}, :map, Ash.Type.Map, {:array, Ash.Type.Map}]))
|
||||||
|
|
|
@ -639,14 +639,53 @@ defmodule AshPhoenix.Form do
|
||||||
Ash.Query.for_read(
|
Ash.Query.for_read(
|
||||||
resource,
|
resource,
|
||||||
action,
|
action,
|
||||||
opts[:params] || %{},
|
params || %{},
|
||||||
query_opts
|
query_opts
|
||||||
)
|
)
|
||||||
|
|> add_errors_for_unhandled_params(params)
|
||||||
}
|
}
|
||||||
|> set_changed?()
|
|> set_changed?()
|
||||||
|> set_validity()
|
|> set_validity()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp add_errors_for_unhandled_params(%{action: nil} = query, _params), do: query
|
||||||
|
|
||||||
|
defp add_errors_for_unhandled_params(query, params) do
|
||||||
|
arguments = Enum.map(query.action.arguments, &to_string(&1.name))
|
||||||
|
|
||||||
|
remaining_params = Map.drop(params, arguments)
|
||||||
|
|
||||||
|
Enum.reduce(remaining_params, query, fn {key, value}, query ->
|
||||||
|
attribute = Ash.Resource.Info.public_attribute(query.resource, key)
|
||||||
|
|
||||||
|
if attribute do
|
||||||
|
case Ash.Changeset.cast_input(attribute.type, value, attribute.constraints, query) do
|
||||||
|
{:ok, casted} ->
|
||||||
|
%{query | params: Map.put(query.params, key, casted)}
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
messages =
|
||||||
|
if Keyword.keyword?(error) do
|
||||||
|
[error]
|
||||||
|
else
|
||||||
|
List.wrap(error)
|
||||||
|
end
|
||||||
|
|
||||||
|
messages
|
||||||
|
|> Enum.reduce(query, fn message, query ->
|
||||||
|
message
|
||||||
|
|> Ash.Changeset.error_to_exception_opts(attribute)
|
||||||
|
|> Enum.reduce(query, fn opts, query ->
|
||||||
|
Ash.Query.add_error(query, Ash.Error.Changes.InvalidAttribute.exception(opts))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
@doc "A utility to get the list of attributes the action underlying the form accepts"
|
@doc "A utility to get the list of attributes the action underlying the form accepts"
|
||||||
def attributes(form) do
|
def attributes(form) do
|
||||||
AshPhoenix.Form.Auto.accepted_attributes(form.resource, form.source.action)
|
AshPhoenix.Form.Auto.accepted_attributes(form.resource, form.source.action)
|
||||||
|
@ -760,6 +799,7 @@ defmodule AshPhoenix.Form do
|
||||||
params,
|
params,
|
||||||
source_opts
|
source_opts
|
||||||
)
|
)
|
||||||
|
|> add_errors_for_unhandled_params(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
@ -2078,8 +2118,26 @@ defmodule AshPhoenix.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_form_resource_and_action(opts, config, key, trail) do
|
defp add_form_resource_and_action(opts, config, key, trail) do
|
||||||
|
default =
|
||||||
|
cond do
|
||||||
|
config[:create_action] && (config[:create_resource] || config[:resource]) ->
|
||||||
|
:create
|
||||||
|
|
||||||
|
config[:read_action] && (config[:read_resource] || config[:resource]) ->
|
||||||
|
:read
|
||||||
|
|
||||||
|
config[:update_action] && (config[:update_resource] || config[:resource]) ->
|
||||||
|
:update
|
||||||
|
|
||||||
|
config[:destroy_action] && (config[:destroy_resource] || config[:resource]) ->
|
||||||
|
:destroy
|
||||||
|
|
||||||
|
true ->
|
||||||
|
:create
|
||||||
|
end
|
||||||
|
|
||||||
action =
|
action =
|
||||||
case opts[:type] || :create do
|
case opts[:type] || default do
|
||||||
:create ->
|
:create ->
|
||||||
config[:create_action] ||
|
config[:create_action] ||
|
||||||
raise AshPhoenix.Form.NoActionConfigured,
|
raise AshPhoenix.Form.NoActionConfigured,
|
||||||
|
@ -2106,7 +2164,7 @@ defmodule AshPhoenix.Form do
|
||||||
end
|
end
|
||||||
|
|
||||||
resource =
|
resource =
|
||||||
case opts[:type] || :create do
|
case opts[:type] || default do
|
||||||
:create ->
|
:create ->
|
||||||
config[:create_resource] || config[:resource] ||
|
config[:create_resource] || config[:resource] ||
|
||||||
raise AshPhoenix.Form.NoResourceConfigured, path: Enum.reverse(trail, [key])
|
raise AshPhoenix.Form.NoResourceConfigured, path: Enum.reverse(trail, [key])
|
||||||
|
|
|
@ -23,6 +23,16 @@ defmodule AshPhoenix.FormTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "a read will validate attributes" do
|
||||||
|
form =
|
||||||
|
Post
|
||||||
|
|> Form.for_read(:read)
|
||||||
|
|> Form.validate(%{"text" => [1, 2, 3]})
|
||||||
|
|> form_for("action")
|
||||||
|
|
||||||
|
assert form.errors[:text] == {"is invalid", []}
|
||||||
|
end
|
||||||
|
|
||||||
test "validation errors are attached to fields" do
|
test "validation errors are attached to fields" do
|
||||||
form = Form.for_create(PostWithDefault, :create, api: Api)
|
form = Form.for_create(PostWithDefault, :create, api: Api)
|
||||||
form = AshPhoenix.Form.validate(form, %{"text" => ""}, errors: form.submitted_once?)
|
form = AshPhoenix.Form.validate(form, %{"text" => ""}, errors: form.submitted_once?)
|
||||||
|
|
Loading…
Reference in a new issue