Packages
ash_phoenix
2.3.22
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/transformers/add_form_code_interfaces.ex
# SPDX-FileCopyrightText: 2020 ash_phoenix contributors <https://github.com/ash-project/ash_phoenix/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule AshPhoenix.Transformers.AddFormCodeInterfaces do
@moduledoc false
use Spark.Dsl.Transformer
def after?(_), do: true
def transform(dsl_state) do
case Ash.Domain.Info.resource_references(dsl_state) do
[] ->
resource = Spark.Dsl.Transformer.get_persisted(dsl_state, :module)
dsl_state
|> Ash.Resource.Info.interfaces()
|> Enum.filter(&match?(%Ash.Resource.Interface{}, &1))
|> Enum.uniq_by(& &1.name)
|> Enum.reduce(dsl_state, &add_form_interface(&1, &2, resource, true))
|> then(&{:ok, &1})
references ->
references
|> Enum.reduce(dsl_state, fn reference, dsl_state ->
reference.definitions
|> Enum.filter(&match?(%Ash.Resource.Interface{}, &1))
|> Enum.uniq_by(& &1.name)
|> Enum.reduce(dsl_state, &add_form_interface(&1, &2, reference.resource))
end)
|> then(&{:ok, &1})
end
end
# sobelow_skip ["DOS.BinToAtom"]
defp add_form_interface(interface, dsl_state, resource, resource? \\ false) do
name = :"form_to_#{interface.name}"
action =
if resource? do
Ash.Resource.Info.action(dsl_state, interface.action || interface.name)
else
Ash.Resource.Info.action(resource, interface.action || interface.name)
end
form = AshPhoenix.Info.form(dsl_state, interface.name)
args = (form && form.args) || []
arg_vars_function =
Enum.map(args, fn
{:optional, key} ->
default = Ash.CodeInterface.default_value(resource, action, key)
{:\\, [], [{key, [], Elixir}, default]}
key ->
{key, [], Elixir}
end)
arg_names =
Enum.map(args, fn
{:optional, name} ->
name
name ->
name
end)
delete = Enum.flat_map(arg_names, &[&1, to_string(&1)])
{private_args, params} =
Enum.split_with(arg_names, fn arg ->
Enum.any?(action.arguments, &(&1.name == arg && !&1.public?))
end)
merge_params = {:%{}, [], Enum.map(params, &{to_string(&1), {&1, [], Elixir}})}
private_args_merge = {:%{}, [], Enum.map(private_args, &{&1, {&1, [], Elixir}})}
cond do
!action ->
dsl_state
action.type in [:update, :destroy] and interface.require_reference? ->
define =
quote do
@doc """
#{unquote(action.description) || "Creates a form for the #{unquote(action.name)} action on #{unquote(inspect(resource))}."}
## Options
#{Spark.Options.docs(AshPhoenix.Form.for_opts())}
Any *additional* options will be passed to the underlying call to build the source, i.e
`Ash.ActionInput.for_action/4`, or `Ash.Changeset.for_*`. This means you can set things
like the tenant/actor. These will be retained, and provided again when
`Form.submit/3` is called.
## Nested Form Options
#{Spark.Options.docs(AshPhoenix.Form.nested_form_opts())}
"""
|> Ash.CodeInterface.trim_double_newlines()
def unquote(name)(record, unquote_splicing(arg_vars_function), form_opts \\ []) do
form_opts =
form_opts
|> unquote(__MODULE__).merge_and_drop_params(
unquote(merge_params),
unquote(delete)
)
|> unquote(__MODULE__).set_private_arguments(unquote(private_args_merge))
AshPhoenix.Form.for_action(record, unquote(action.name), form_opts)
end
end
Spark.Dsl.Transformer.eval(dsl_state, [], define)
true ->
define =
quote do
@doc """
#{unquote(action.description) || "Creates a form for the #{unquote(action.name)} action on #{unquote(inspect(resource))}."}
## Options
#{Spark.Options.docs(AshPhoenix.Form.for_opts())}
Any *additional* options will be passed to the underlying call to build the source, i.e
`Ash.ActionInput.for_action/4`, or `Ash.Changeset.for_*`. This means you can set things
like the tenant/actor. These will be retained, and provided again when
`Form.submit/3` is called.
## Nested Form Options
#{Spark.Options.docs(AshPhoenix.Form.nested_form_opts())}
"""
|> Ash.CodeInterface.trim_double_newlines()
def unquote(name)(unquote_splicing(arg_vars_function), form_opts \\ []) do
# Only transform the argument params (merge_params), not all form params
transformed_merge_params =
unquote(__MODULE__).handle_custom_inputs(
unquote(merge_params),
unquote(Macro.escape(interface.custom_inputs))
)
form_opts =
form_opts
|> unquote(__MODULE__).merge_and_drop_params(
transformed_merge_params,
unquote(delete)
)
|> unquote(__MODULE__).set_private_arguments(unquote(private_args_merge))
AshPhoenix.Form.for_action(unquote(resource), unquote(action.name), form_opts)
|> Map.update!(:params, fn params ->
params
|> Map.drop(unquote(delete))
|> Map.merge(transformed_merge_params)
end)
|> Map.update!(:raw_params, fn params ->
params
|> Map.drop(unquote(delete))
|> Map.merge(transformed_merge_params)
end)
end
end
Spark.Dsl.Transformer.eval(dsl_state, [], define)
end
end
@doc false
def merge_and_drop_params(opts, merge_params, delete)
when merge_params == %{} and delete == [] do
opts
end
def merge_and_drop_params(opts, merge_params, delete) do
opts
|> Keyword.update(:params, merge_params, fn existing_params ->
existing_params
|> Kernel.||(%{})
|> Map.delete(delete)
|> Map.merge(merge_params)
end)
|> Keyword.update(
:transform_params,
fn _form, params, _ ->
params
|> Map.drop(delete)
|> Map.merge(merge_params)
end,
fn existing ->
existing =
existing ||
fn _, params, _ ->
params
end
fn form, params, type ->
params =
params
|> Map.drop(delete)
|> Map.merge(merge_params)
if is_function(existing, 2) do
existing.(params, type)
else
existing.(form, params, type)
end
end
end
)
end
@doc false
def set_private_arguments(opts, empty) when empty == %{} do
opts
end
def set_private_arguments(opts, private_args_merge) do
Keyword.update(
opts,
:private_arguments,
private_args_merge,
&Map.merge(&1 || %{}, private_args_merge)
)
end
@doc false
def handle_custom_inputs(params, []) do
params
end
def handle_custom_inputs(params, custom_inputs) do
Enum.reduce(custom_inputs, params, fn custom_input, acc_params ->
case fetch_key(acc_params, custom_input.name) do
{:ok, key, value} ->
value = Ash.Type.Helpers.handle_indexed_maps(custom_input.type, value)
case Ash.Type.cast_input(custom_input.type, value, custom_input.constraints) do
{:ok, casted} ->
case Ash.Type.apply_constraints(custom_input.type, casted, custom_input.constraints) do
{:ok, casted} ->
if is_nil(casted) && !custom_input.allow_nil? do
acc_params
else
apply_custom_input_transform(acc_params, casted, key, custom_input)
end
_error ->
acc_params
end
_error ->
acc_params
end
:error ->
acc_params
end
end)
end
defp apply_custom_input_transform(params, casted, key, %{transform: nil}) do
Map.put(params, key, casted)
end
defp apply_custom_input_transform(params, casted, key, %{
transform: %{to: nil, using: nil}
}) do
Map.put(params, key, casted)
end
defp apply_custom_input_transform(params, casted, key, %{
transform: %{to: to, using: nil}
}) do
params
|> Map.delete(key)
|> Map.put(to_string(to), casted)
end
defp apply_custom_input_transform(params, casted, key, %{
transform: %{to: nil, using: using}
}) do
Map.put(params, key, using.(casted))
end
defp apply_custom_input_transform(params, casted, key, %{
transform: %{to: to, using: using}
}) do
params
|> Map.delete(key)
|> Map.put(to_string(to), using.(casted))
end
defp fetch_key(map, key) do
with {_key, :error} <- {key, Map.fetch(map, key)},
string_key = to_string(key),
{_key, :error} <- {string_key, Map.fetch(map, string_key)} do
:error
else
{key, {:ok, value}} ->
{:ok, key, value}
end
end
end