mirror of
https://github.com/ash-project/ash_phoenix.git
synced 2024-09-19 23:02:48 +12:00
WIP
This commit is contained in:
parent
eccb60ed20
commit
6da47f7d1d
4 changed files with 669 additions and 45 deletions
|
@ -3,8 +3,9 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
|
|||
# The goal here is to eventually lift complex validations
|
||||
# up into the form.
|
||||
|
||||
@impl true
|
||||
def input_type(%{resource: resource, action: action}, _, field) do
|
||||
attribute = Ash.Resource.attribute(resource, field)
|
||||
attribute = Ash.Resource.Info.attribute(resource, field)
|
||||
|
||||
if attribute do
|
||||
type_to_form_type(attribute.type)
|
||||
|
@ -39,45 +40,37 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
|
|||
end
|
||||
end
|
||||
|
||||
def input_validations(_changeset, _form, _field) do
|
||||
[]
|
||||
end
|
||||
|
||||
# # Returns the HTML5 validations that would apply to the given field.
|
||||
|
||||
def input_value(changeset, _form, field) do
|
||||
params_only? = field in (changeset.context[:params_only] || [])
|
||||
|
||||
case get_changing_value(changeset, field, params_only?) do
|
||||
@impl true
|
||||
def input_value(changeset, form, field) do
|
||||
case Keyword.fetch(form.options, :value) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
value || ""
|
||||
|
||||
:error ->
|
||||
unless params_only? do
|
||||
Map.get(changeset.data, field)
|
||||
_ ->
|
||||
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, params_only?) do
|
||||
if params_only? do
|
||||
case Map.fetch(changeset.params, field) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
:error ->
|
||||
Map.fetch(changeset.params, to_string(field))
|
||||
end
|
||||
else
|
||||
with :error <- Map.fetch(changeset.attributes, field),
|
||||
:error <- Map.fetch(changeset.params, field) do
|
||||
Map.fetch(changeset.params, to_string(field))
|
||||
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
|
||||
|
||||
# # Returns the value for the given field.
|
||||
|
||||
@impl true
|
||||
def to_form(changeset, opts) do
|
||||
{name, opts} = Keyword.pop(opts, :as)
|
||||
|
||||
|
@ -85,12 +78,17 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
|
|||
id = Keyword.get(opts, :id) || name
|
||||
|
||||
hidden =
|
||||
changeset.data
|
||||
|> Map.take(Ash.Resource.primary_key(changeset.resource))
|
||||
|> Enum.to_list()
|
||||
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{
|
||||
source: changeset,
|
||||
action: changeset.action.name,
|
||||
source: Ash.Changeset.put_context(changeset, :form, %{path: []}),
|
||||
impl: __MODULE__,
|
||||
id: id,
|
||||
name: name,
|
||||
|
@ -98,15 +96,636 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
|
|||
data: changeset.data,
|
||||
params: changeset.params,
|
||||
hidden: hidden,
|
||||
options:
|
||||
Keyword.delete(Keyword.put_new(opts, :method, form_for_method(changeset)), :only_params)
|
||||
options: Keyword.put_new(opts, :method, form_for_method(changeset))
|
||||
}
|
||||
end
|
||||
|
||||
def to_form(_changeset, _form, _field, _opts) do
|
||||
[]
|
||||
@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}]")
|
||||
|
||||
{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, attr}, resource, data, opts}
|
||||
end
|
||||
|
||||
arg =
|
||||
Enum.find(
|
||||
changeset.action.arguments,
|
||||
&(&1.name == field || to_string(&1.name) == field)
|
||||
) ->
|
||||
case get_embedded(arg.type) do
|
||||
nil ->
|
||||
case get_managed_relationship(changeset.resource, changeset.action, arg.name) do
|
||||
nil ->
|
||||
raise "Cannot use `form_for` with an argument unless the type is an embedded resource, or unless there is a `manage_relationship` change that references the argument"
|
||||
|
||||
{relationship, manage_opts} ->
|
||||
data = Map.get(changeset.relationships, relationship.name)
|
||||
|
||||
data =
|
||||
case relationship.cardinality do
|
||||
:many ->
|
||||
List.wrap(data)
|
||||
|
||||
_ when is_list(data) ->
|
||||
Enum.at(data, 0)
|
||||
|
||||
_ ->
|
||||
data
|
||||
end
|
||||
|
||||
{{:manage_relationship, relationship}, relationship.destination, data,
|
||||
Keyword.merge(manage_opts, opts)}
|
||||
end
|
||||
|
||||
resource ->
|
||||
data = Ash.Changeset.get_argument(changeset, arg.name)
|
||||
|
||||
data =
|
||||
case arg.type do
|
||||
{:array, _} ->
|
||||
List.wrap(data)
|
||||
|
||||
_ ->
|
||||
data
|
||||
end
|
||||
|
||||
{{:embed_arg, arg}, resource, data}
|
||||
end
|
||||
|
||||
relationship = Ash.Resource.Info.relationship(changeset.resource, field) ->
|
||||
data = relationship_data(changeset, relationship, changeset_opts)
|
||||
|
||||
data =
|
||||
case relationship.cardinality do
|
||||
:many ->
|
||||
List.wrap(data)
|
||||
|
||||
_ when is_list(data) ->
|
||||
Enum.at(data, 0)
|
||||
|
||||
_ ->
|
||||
data
|
||||
end
|
||||
|
||||
{{:manage_relationship, relationship}, relationship.destination, data, opts}
|
||||
end
|
||||
|
||||
data =
|
||||
if is_list(data) do
|
||||
prepend ++ data ++ append
|
||||
else
|
||||
data
|
||||
end
|
||||
|
||||
changeset
|
||||
|> to_nested_form(data, source, resource, id, name, opts, changeset_opts)
|
||||
|> List.wrap()
|
||||
end
|
||||
|
||||
defp relationship_data(changeset, relationship, changeset_opts) do
|
||||
value =
|
||||
case Map.get(changeset.data, relationship.name) do
|
||||
%Ash.NotLoaded{} ->
|
||||
[]
|
||||
|
||||
value ->
|
||||
value
|
||||
end
|
||||
|
||||
case relationship.type do
|
||||
:many_to_many ->
|
||||
join_relationship =
|
||||
Ash.Resource.Info.relationship(relationship.destination, relationship.destination)
|
||||
|
||||
value
|
||||
|> List.wrap()
|
||||
|> Enum.map(&Ash.Changeset.new/1)
|
||||
|> Enum.map(&Ash.Changeset.set_context(&1, relationship.context))
|
||||
|> Enum.map(fn value_changeset ->
|
||||
case Map.get(changeset.data, join_relationship.name) do
|
||||
%Ash.NotLoaded{} ->
|
||||
value_changeset
|
||||
|
||||
value ->
|
||||
value
|
||||
|> Enum.find(fn join_row ->
|
||||
Map.get(join_row, relationship.source_field_on_join_table) ==
|
||||
Map.get(changeset.data, relationship.source_field) &&
|
||||
Map.get(join_row, relationship) ==
|
||||
Map.get(changeset.data, relationship.destination_field)
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
value_changeset
|
||||
|
||||
join_row ->
|
||||
join_changeset =
|
||||
join_row
|
||||
|> Ash.Changeset.new()
|
||||
|> Ash.Changeset.set_context(join_relationship.context)
|
||||
|
||||
Ash.Changeset.put_context(changeset, :private, %{
|
||||
join_changeset: join_changeset
|
||||
})
|
||||
end
|
||||
end
|
||||
end)
|
||||
|> apply_relationship_instructions(changeset, relationship, changeset_opts)
|
||||
|
||||
_ ->
|
||||
value
|
||||
|> List.wrap()
|
||||
|> Enum.map(&Ash.Changeset.new/1)
|
||||
|> apply_relationship_instructions(changeset, relationship, changeset_opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp apply_relationship_instructions(value, changeset, relationship, changeset_opts) do
|
||||
changeset.relationships
|
||||
|> Map.get(relationship.name)
|
||||
|> List.wrap()
|
||||
|> Enum.reduce(value, fn {changes, opts}, value ->
|
||||
apply_relationship_change(value, changes, relationship, opts, changeset_opts)
|
||||
end)
|
||||
end
|
||||
|
||||
defp apply_relationship_change(value, manage_value, relationship, opts, changeset_opts) do
|
||||
pkeys = pkeys(relationship)
|
||||
|
||||
{relationship_value, unused_inputs} =
|
||||
Enum.reduce(value, {[], manage_value}, fn changeset, {acc, manage_value} ->
|
||||
case find_match(manage_value, changeset, pkeys) do
|
||||
nil ->
|
||||
case opts[:on_missing] do
|
||||
instruction when instruction in [:error, :ignore] ->
|
||||
{[changeset | acc], manage_value}
|
||||
|
||||
_ ->
|
||||
{acc, manage_value}
|
||||
end
|
||||
|
||||
match ->
|
||||
case opts[:on_match] do
|
||||
instruction when instruction in [:error, :ignore] ->
|
||||
{[changeset | acc], manage_value -- [match]}
|
||||
|
||||
instruction when instruction in [:destroy, :unrelate] ->
|
||||
{acc, manage_value -- match}
|
||||
|
||||
:create ->
|
||||
{acc, manage_value}
|
||||
|
||||
{:unrelate, _} ->
|
||||
{acc, manage_value}
|
||||
|
||||
:update ->
|
||||
action_name = Ash.Resource.Info.primary_action!(changeset.resource, :update).name
|
||||
|
||||
{[Ash.Changeset.for_update(changeset, action_name, match, changeset_opts) | acc],
|
||||
[match | manage_value]}
|
||||
|
||||
{:update, action_name} ->
|
||||
{[Ash.Changeset.for_update(changeset, action_name, match, changeset_opts) | acc],
|
||||
[match | manage_value]}
|
||||
|
||||
{:update, action_name, join_table_action_name, params} ->
|
||||
join_row =
|
||||
case changeset.context[:private][:join_row] do
|
||||
nil ->
|
||||
raise "The join relationship must be loaded if using `inputs_for` with a managed relationship that specifies a join action/params"
|
||||
|
||||
join_row ->
|
||||
Ash.Changeset.for_update(
|
||||
join_row,
|
||||
join_table_action_name,
|
||||
Map.take(match, params ++ Enum.map(params, &to_string/1)),
|
||||
changeset_opts
|
||||
)
|
||||
end
|
||||
|
||||
changeset =
|
||||
changeset
|
||||
|> Ash.Changeset.for_update(action_name, match, changeset_opts)
|
||||
|> Ash.Changeset.put_context(:private, %{join_row: join_row})
|
||||
|
||||
{[changeset | acc], manage_value -- [match]}
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
new_changesets =
|
||||
if opts[:on_no_match] in [:ignore, :error] do
|
||||
[]
|
||||
else
|
||||
for input <- unused_inputs do
|
||||
case opts[:on_no_match] do
|
||||
:create ->
|
||||
action = Ash.Resource.Info.primary_action!(relationship.destination, :create)
|
||||
|
||||
Ash.Changeset.for_create(
|
||||
relationship.destination,
|
||||
action.name,
|
||||
input,
|
||||
changeset_opts
|
||||
)
|
||||
|
||||
{:create, action_name} ->
|
||||
Ash.Changeset.for_create(
|
||||
relationship.destination,
|
||||
action_name,
|
||||
input,
|
||||
changeset_opts
|
||||
)
|
||||
|
||||
{:create, action_name, join_table_action_name, params} ->
|
||||
join_row =
|
||||
Ash.Changeset.for_create(
|
||||
relationship.through,
|
||||
join_table_action_name,
|
||||
Map.take(input, params ++ Enum.map(params, &to_string/1)),
|
||||
changeset_opts
|
||||
)
|
||||
|
||||
relationship.destination
|
||||
|> Ash.Changeset.for_create(action_name, input, changeset_opts)
|
||||
|> Ash.Changeset.put_context(:private, %{join_row: join_row})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Enum.reverse(relationship_value, new_changesets)
|
||||
end
|
||||
|
||||
defp get_managed_relationship(resource, action, arg_name) do
|
||||
action.changes
|
||||
|> Enum.find(fn
|
||||
%{change: {Ash.Resource.Change.ManageRelationship, opts}} ->
|
||||
opts[:argument] == arg_name
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
%{change: {_, opts}} ->
|
||||
{Ash.Resource.Info.relationship(resource, opts[:relationship_name]), opts[:opts]}
|
||||
end
|
||||
end
|
||||
|
||||
defp pkeys(relationship) do
|
||||
identities =
|
||||
relationship.destination
|
||||
|> Ash.Resource.Info.identities()
|
||||
|> Enum.map(& &1.keys)
|
||||
|
||||
[Ash.Resource.Info.primary_key(relationship.destination) | identities]
|
||||
end
|
||||
|
||||
defp find_match(current_value, input, pkeys) do
|
||||
Enum.find(current_value, fn
|
||||
%Ash.NotLoaded{} ->
|
||||
false
|
||||
|
||||
loaded ->
|
||||
Enum.any?(pkeys, fn pkey ->
|
||||
matches?(loaded, input, pkey)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp matches?(current_value, input, pkey) do
|
||||
Enum.all?(pkey, fn field ->
|
||||
with {:ok, left} <- fetch_field(current_value, field),
|
||||
{:ok, right} <- fetch_field(input, field) do
|
||||
left == right
|
||||
else
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp fetch_field(input, field) do
|
||||
case Map.fetch(input, field) do
|
||||
{:ok, value} ->
|
||||
{:ok, value}
|
||||
|
||||
:error ->
|
||||
Map.fetch(input, to_string(field))
|
||||
end
|
||||
end
|
||||
|
||||
defp to_nested_form(
|
||||
original_changeset,
|
||||
changesets,
|
||||
{:manage_relationship, %{cardinality: :many} = rel},
|
||||
_resource,
|
||||
id,
|
||||
name,
|
||||
opts,
|
||||
_changeset_opts
|
||||
) do
|
||||
changesets
|
||||
|> Enum.map(&customize_changeset(&1, original_changeset, :relationship, rel))
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {changeset, index} ->
|
||||
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.name,
|
||||
source: changeset,
|
||||
impl: __MODULE__,
|
||||
id: id <> "_" <> index_string,
|
||||
name: name <> "[" <> index_string <> "]",
|
||||
index: index,
|
||||
errors: form_for_errors(changeset, opts),
|
||||
data: changeset.data,
|
||||
params: changeset.params,
|
||||
hidden: hidden,
|
||||
options: opts |> IO.inspect()
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_nested_form(
|
||||
original_changeset,
|
||||
changeset,
|
||||
{:manage_relationship, %{cardinality: :one} = rel},
|
||||
_resource,
|
||||
id,
|
||||
name,
|
||||
opts,
|
||||
_changeset_opts
|
||||
) do
|
||||
if changeset do
|
||||
changeset = customize_changeset(changeset, original_changeset, :relationship, rel)
|
||||
|
||||
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: changeset.data,
|
||||
params: changeset.params,
|
||||
hidden: hidden,
|
||||
options: opts
|
||||
}
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp to_nested_form(
|
||||
original_changeset,
|
||||
data,
|
||||
{type, attribute},
|
||||
resource,
|
||||
id,
|
||||
name,
|
||||
opts,
|
||||
changeset_opts
|
||||
)
|
||||
when is_list(data) and type in [:attr, :embed_arg] 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)
|
||||
|> Enum.map(&customize_changeset(&1, original_changeset, :embed, attribute))
|
||||
|
||||
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.name,
|
||||
source:
|
||||
Ash.Changeset.put_context(changeset, :form, %{
|
||||
path: (original_changeset.context[:form][:path] || []) ++ [attribute.name]
|
||||
}),
|
||||
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(
|
||||
original_changeset,
|
||||
data,
|
||||
{type, attribute},
|
||||
resource,
|
||||
id,
|
||||
name,
|
||||
opts,
|
||||
changeset_opts
|
||||
)
|
||||
when type in [:attr, :embed_arg] 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
|
||||
|
||||
changeset = customize_changeset(changeset, original_changeset, :embed, attribute)
|
||||
|
||||
if changeset do
|
||||
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{
|
||||
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 customize_changeset(nil, _, _, _), do: nil
|
||||
|
||||
defp customize_changeset(changeset, original_changeset, type, attribute_or_relationship) do
|
||||
customize = original_changeset.context[:form][:customize_changeset]
|
||||
|
||||
changeset =
|
||||
Ash.Changeset.set_context(changeset, %{
|
||||
customize_changeset: customize,
|
||||
form: %{
|
||||
path:
|
||||
(original_changeset.context[:form][:path] || []) ++ [attribute_or_relationship.name]
|
||||
}
|
||||
})
|
||||
|
||||
case customize do
|
||||
nil ->
|
||||
changeset
|
||||
|
||||
function when is_function(function, 3) ->
|
||||
function.(changeset, type, attribute_or_relationship)
|
||||
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_errors(changeset, opts) do
|
||||
changeset.errors
|
||||
|> Enum.filter(&(Map.has_key?(&1, :field) || Map.has_key?(&1, :fields)))
|
||||
|
@ -149,12 +768,16 @@ defimpl Phoenix.HTML.FormData, for: Ash.Changeset do
|
|||
else
|
||||
Enum.filter(errors, fn {field, _} ->
|
||||
field in (opts[:error_keys] || []) ||
|
||||
Map.has_key?(changeset.params, field) ||
|
||||
Map.has_key?(changeset.params, to_string(field))
|
||||
has_non_empty_key?(changeset.params, field) ||
|
||||
has_non_empty_key?(changeset.params, to_string(field))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp has_non_empty_key?(params, field) do
|
||||
Map.has_key?(params, field) && params[field] not in [nil, "", []]
|
||||
end
|
||||
|
||||
defp vars(%{vars: vars}, opts) do
|
||||
Keyword.merge(vars, opts)
|
||||
end
|
||||
|
|
|
@ -436,7 +436,7 @@ defmodule AshPhoenix.LiveView do
|
|||
nil
|
||||
|
||||
first = List.first(current_list).__struct__
|
||||
pkey = Ash.Resource.primary_key(first)
|
||||
pkey = Ash.Resource.Info.primary_key(first)
|
||||
|
||||
resulting_page = run_callback(callback, socket, nil)
|
||||
|
||||
|
@ -460,7 +460,7 @@ defmodule AshPhoenix.LiveView do
|
|||
|
||||
true ->
|
||||
first = List.first(current_page.results).__struct__
|
||||
pkey = Ash.Resource.primary_key(first)
|
||||
pkey = Ash.Resource.Info.primary_key(first)
|
||||
|
||||
filter =
|
||||
case pkey do
|
||||
|
|
3
mix.exs
3
mix.exs
|
@ -72,7 +72,8 @@ defmodule AshPhoenix.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:ash, ash_version("~> 1.31 and >= 1.31.1")},
|
||||
# {:ash, ash_version("~> 1.31 and >= 1.31.1")},
|
||||
{:ash, path: "../ash"},
|
||||
{:phoenix, "~> 1.5.6"},
|
||||
{:phoenix_html, "~> 2.14"},
|
||||
{:phoenix_live_view, "~> 0.15"},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -8,7 +8,7 @@
|
|||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
|
||||
"ecto": {:hex, :ecto, "3.5.6", "29c77e999e471921c7ce7347732bab7bfa3e24c587640a36f17e0744d1474b8e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3ae1f3eaecc3e72eeb65ed43239b292bb1eaf335c7e6cea3a7fc27aadb6e93e7"},
|
||||
"ecto": {:hex, :ecto, "3.5.7", "f440a476bf1be361173a43a4a18f04a2fdf4e6fac5b0457f03d8686e55f13f7e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "04c4e69d4f1cc2bb085aa760d50389ba8ae3003f80c112fbde87d57f5ed75d39"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"},
|
||||
|
|
Loading…
Reference in a new issue