Packages
ash_phoenix
0.4.23-rc.0
2.3.24
2.3.23
2.3.22
2.3.21
2.3.20
2.3.19
2.3.18
2.3.17
2.3.16
2.3.15
2.3.14
2.3.13
2.3.12
2.3.11
2.3.10
2.3.9
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.0
2.1.26
2.1.25
2.1.24
2.1.23
2.1.22
2.1.21
2.1.20
2.1.19
2.1.18
2.1.17
2.1.16
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
1.3.7
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.26
1.2.25
1.2.24
1.2.23
1.2.22
1.2.21
1.2.20
1.2.19
1.2.18
1.2.17
1.2.16
1.2.15
1.2.14
1.2.13
1.2.12
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.1.0-rc.3
1.1.0-rc.2
1.1.0-rc.1
1.1.0-rc.0
1.0.0-rc.1
1.0.0-rc.0
1.0.0-pre.2
1.0.0-pre.1
1.0.0-pre.0
0.7.7
0.7.6-rc.0
0.7.5
0.7.4
0.7.3
0.7.2-rc.2
0.7.2-rc.1
0.7.2-rc.0
0.7.1
0.7.0
0.6.0-rc.7
0.6.0-rc.6
0.6.0-rc.5
0.6.0-rc.4
0.6.0-rc.3
0.6.0-rc.2
0.6.0-rc.1
0.6.0-rc.0
0.5.19-rc.2
0.5.19-rc.1
0.5.19-rc.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.24
0.4.23
0.4.23-rc.1
0.4.23-rc.0
0.4.22-rc2
0.4.22-rc1
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Utilities for integrating Ash and Phoenix
Current section
Files
Jump to
Current section
Files
lib/ash_phoenix/form_data/changeset.ex
defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
import AshPhoenix.FormData.Helpers
@impl true
def input_type(%{resource: resource, action: action}, _, field) do
attribute = Ash.Resource.Info.attribute(resource, field)
if attribute do
type_to_form_type(attribute.type)
else
argument = get_argument(action, field)
if argument do
type_to_form_type(argument.type)
else
:text_input
end
end
end
@impl true
def input_value(changeset, _form, field) do
case get_changing_value(changeset, field) do
{:ok, value} ->
value
:error ->
case Map.fetch(changeset.data, field) do
{:ok, value} ->
value
_ ->
Ash.Changeset.get_argument(changeset, field)
end
end
end
defp get_changing_value(changeset, field) do
with :error <- Map.fetch(changeset.attributes, field),
:error <- Map.fetch(changeset.params, field) do
Map.fetch(changeset.params, to_string(field))
end
end
@impl true
def to_form(changeset, opts) do
{name, opts} = Keyword.pop(opts, :as)
name = to_string(name || form_for_name(changeset.resource))
id = Keyword.get(opts, :id) || name
hidden =
if changeset.action_type in [:update, :destroy] do
changeset.data
|> Map.take(Ash.Resource.Info.primary_key(changeset.resource))
|> Enum.to_list()
else
[]
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.||(%{})
|> Enum.filter(&elem(&1, 1))
|> Enum.map(fn {name, _} -> {name, nil} end)
hidden = hidden ++ removed_embed_values
%Phoenix.HTML.Form{
source: changeset,
impl: __MODULE__,
id: id,
name: name,
errors: form_for_errors(changeset, opts),
data: changeset.data,
params: changeset.params,
hidden: hidden,
options: Keyword.put_new(opts, :method, form_for_method(changeset))
}
end
@impl true
def to_form(changeset, form, field, opts) do
{name, opts} = Keyword.pop(opts, :as)
{id, opts} = Keyword.pop(opts, :id)
{prepend, opts} = Keyword.pop(opts, :prepend, [])
{append, opts} = Keyword.pop(opts, :append, [])
{use_data?, opts} = Keyword.pop(opts, :use_data?, false)
id = to_string(id || form.id <> "_#{field}")
name = to_string(name || form.name <> "[#{field}]")
{source, resource, data} =
cond do
arg = changeset.action && get_argument(changeset.action, field) ->
case argument_and_manages(changeset, arg.name) do
{nil, _} ->
case get_embedded(arg.type) do
nil ->
raise "Cannot use `form_for` with an argument unless the type is an embedded resource or that argument manages a relationship"
resource ->
data = Ash.Changeset.get_argument(changeset, arg.name)
data =
case arg.type do
{:array, _} ->
List.wrap(data)
_ ->
data
end
{arg, resource, data}
end
{argument, rel} ->
if rel do
rel = Ash.Resource.Info.relationship(changeset.resource, rel)
data =
relationship_data(
changeset,
rel,
use_data?,
opts[:id] || argument.name || rel.name
)
data =
case argument.type do
{:array, _} ->
List.wrap(data)
_ ->
if is_list(data) do
List.last(data)
else
data
end
end
{rel, rel.destination, data}
else
raise "Cannot use `form_for` with an argument unless the type is an embedded resource or that argument manages a relationship"
end
end
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) ->
case get_embedded(attr.type) do
nil ->
raise "Cannot use `form_for` with an attribute unless the type is an embedded resource"
resource ->
data = Ash.Changeset.get_attribute(changeset, attr.name)
data =
case attr.type do
{:array, _} ->
List.wrap(data)
_ ->
data
end
{attr, resource, data}
end
true ->
raise "Cannot use `form_for` with anything except embedded resources in attributes/arguments"
end
data =
if is_list(data) do
prepend ++ data ++ append
else
unwrap(prepend) || unwrap(append) || data
end
data
|> to_nested_form(changeset, source, resource, id, name, opts)
|> List.wrap()
end
defp unwrap([]), do: nil
defp unwrap([value | _]), do: value
defp unwrap(value), do: value
@impl true
def input_validations(changeset, _, field) do
attribute_or_argument =
Ash.Resource.Info.attribute(changeset.resource, field) ||
get_argument(changeset.action, field)
if attribute_or_argument do
[required: !attribute_or_argument.allow_nil?] ++ type_validations(attribute_or_argument)
else
[]
end
end
defp type_validations(%{type: Ash.Types.Integer, constraints: constraints}) do
constraints
|> Kernel.||([])
|> Keyword.take([:max, :min])
|> Keyword.put(:step, 1)
end
defp type_validations(%{type: Ash.Types.Decimal, constraints: constraints}) do
constraints
|> Kernel.||([])
|> Keyword.take([:max, :min])
|> Keyword.put(:step, "any")
end
defp type_validations(%{type: Ash.Types.String, constraints: constraints}) do
if constraints[:trim?] do
# We should consider using the `match` validation here, but we can't
# add a title here, so we can't set an error message
# min_length = to_string(constraints[:min_length])
# max_length = to_string(constraints[:max_length])
# [match: "(\S\s*){#{min_length},#{max_length}}"]
[]
else
validations =
if constraints[:min_length] do
[min_length: constraints[:min_length]]
else
[]
end
if constraints[:min_length] do
Keyword.put(constraints, :min_length, constraints[:min_length])
else
validations
end
end
end
defp type_validations(_), do: []
defp form_for_method(%{action_type: :create}), do: "post"
defp form_for_method(_), do: "put"
end