Packages
ash_phoenix
0.3.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 Keyword.fetch(form.options, :value) do
{:ok, value} ->
value || ""
_ ->
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
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 == :update do
changeset.data
|> Map.take(Ash.Resource.Info.primary_key(changeset.resource))
|> Enum.to_list()
else
[]
end
%Phoenix.HTML.Form{
action: changeset.action && changeset.action.name,
source: Ash.Changeset.put_context(changeset, :form, %{path: []}),
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, [])
changeset_opts = [skip_defaults: :all]
id = to_string(id || form.id <> "_#{field}")
name = to_string(name || form.name <> "[#{field}]")
arguments =
if changeset.action do
changeset.action.arguments
else
[]
end
{source, resource, data, opts} =
cond do
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, opts}
end
arg =
Enum.find(
arguments,
&(&1.name == field || to_string(&1.name) == field)
) ->
case get_embedded(arg.type) do
nil ->
raise "Cannot use `form_for` with an argument unless the type is an embedded resource"
resource ->
data = Ash.Changeset.get_argument(changeset, arg.name)
data =
case arg.type do
{:array, _} ->
List.wrap(data)
_ ->
data
end
{arg, 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
data
end
data
|> to_nested_form(changeset, source, resource, id, name, opts, changeset_opts)
|> List.wrap()
end
defp to_nested_form(
data,
original_changeset,
attribute,
resource,
id,
name,
opts,
changeset_opts
)
when is_list(data) do
create_action =
attribute.constraints[:create_action] ||
Ash.Resource.Info.primary_action!(resource, :create).name
update_action =
attribute.constraints[:update_action] ||
Ash.Resource.Info.primary_action!(resource, :update).name
changesets =
data
|> Enum.map(fn data ->
if is_struct(data) do
Ash.Changeset.for_update(data, update_action, %{}, changeset_opts)
else
Ash.Changeset.for_create(resource, create_action, data, changeset_opts)
end
end)
changesets =
if AshPhoenix.hiding_errors?(original_changeset) do
Enum.map(changesets, &AshPhoenix.hide_errors/1)
else
changesets
end
for {changeset, index} <- Enum.with_index(changesets) do
index_string = Integer.to_string(index)
hidden =
if changeset.action_type == :update do
changeset.data
|> Map.take(Ash.Resource.Info.primary_key(changeset.resource))
|> Enum.to_list()
else
[]
end
%Phoenix.HTML.Form{
action: changeset.action && changeset.action.name,
source: changeset,
impl: __MODULE__,
id: id <> "_" <> index_string,
name: name <> "[" <> index_string <> "]",
index: index,
errors: form_for_errors(changeset, opts),
data: data,
params: changeset.params,
hidden: hidden,
options: opts
}
end
end
defp to_nested_form(
data,
original_changeset,
attribute,
resource,
id,
name,
opts,
changeset_opts
) do
create_action =
attribute.constraints[:create_action] ||
Ash.Resource.Info.primary_action!(resource, :create).name
update_action =
attribute.constraints[:update_action] ||
Ash.Resource.Info.primary_action!(resource, :update).name
changeset =
cond do
is_struct(data) ->
Ash.Changeset.for_update(data, update_action, %{}, changeset_opts)
is_nil(data) ->
nil
true ->
Ash.Changeset.for_create(resource, create_action, data, changeset_opts)
end
if changeset do
changeset =
if AshPhoenix.hiding_errors?(original_changeset) do
AshPhoenix.hide_errors(changeset)
else
changeset
end
hidden =
if changeset.action_type == :update do
changeset.data
|> Map.take(Ash.Resource.Info.primary_key(changeset.resource))
|> Enum.to_list()
else
[]
end
%Phoenix.HTML.Form{
action: changeset.action && changeset.action.name,
source: changeset,
impl: __MODULE__,
id: id,
name: name,
errors: form_for_errors(changeset, opts),
data: data,
params: changeset.params,
hidden: hidden,
options: opts
}
end
end
defp get_embedded({:array, type}), do: get_embedded(type)
defp get_embedded(type) when is_atom(type) do
if Ash.Resource.Info.embedded?(type) do
type
end
end
defp get_embedded(_), do: nil
@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