# SPDX-FileCopyrightText: 2020 ash_phoenix contributors <https://github.com/ash-project/ash_phoenix/graphs.contributors>
#
# SPDX-License-Identifier: MIT
defmodule AshPhoenix do
@moduledoc """
An extension to add form builders to the code interface.
There is currently no DSL for this extension.
This defines a `form_to_<name>` function for each code interface
function. Arguments are processed according to any custom input
transformations defined on the code interface, while the `params`
option remains untouched.
The generated function passes all options through to
`AshPhoenix.Form.for_action/3`
Update and destroy actions take the record being updated/destroyed
as the first argument.
For example, given this code interface definition on a domain
called `MyApp.Accounts`:
```elixir
resources do
resource MyApp.Accounts.User do
define :register_with_password, args: [:email, :password]
define :update_user, action: :update, args: [:email, :password]
end
end
```
Adding the `AshPhoenix` extension would define
`form_to_register_with_password/2`.
## Custom Input Transformations
If your code interface defines custom inputs with transformations,
the form interface will honor those transformations for arguments,
but not for params passed via the `params` option:
```elixir
# In your domain
resource MyApp.Blog.Comment do
define :create_with_post do
action :create_with_post_id
args [:post]
custom_input :post, :struct do
constraints instance_of: MyApp.Blog.Post
transform to: :post_id, using: & &1.id
end
end
end
# Usage - the post argument will be transformed
form = MyApp.Blog.form_to_create_with_post(
%MyApp.Blog.Post{id: "some-id"},
params: %{"text" => "Hello world"}
)
# The post struct gets transformed to post_id in the form
# The params remain unchanged
```
## Usage
Without options:
```elixir
MyApp.Accounts.form_to_register_with_password()
#=> %AshPhoenix.Form{}
```
With options:
```elixir
MyApp.Accounts.form_to_register_with_password(params: %{"email" => "placeholder@email"})
#=> %AshPhoenix.Form{}
```
For update/destroy actions, the record is required as the first parameter:
```elixir
user = MyApp.Accounts.get_user!(id)
MyApp.Accounts.form_to_update_user(user)
#=> %AshPhoenix.Form{}
```
Update/destroy with options
```elixir
user = MyApp.Accounts.get_user!(id)
MyApp.Accounts.form_to_update_user(user, params: %{"email" => "placeholder@email"})
#=> %AshPhoenix.Form{}
```
"""
defmodule FormDefinition do
@moduledoc "A customized form code interface"
defstruct [:name, :args, :__spark_metadata__]
end
@form %Spark.Dsl.Entity{
name: :form,
target: FormDefinition,
describe: "Customize the definition of a form for a code inteface",
examples: [
"""
# customize the generated `form_to_create_student` function
# args defaults to empty for form definitions
form :create_student, args: [:school_id]
"""
],
args: [:name],
schema: [
name: [
type: :atom,
doc: "The name of the interface to modify. Must match an existing interface definition."
],
args: [
type: {:list, {:or, [:atom, {:tagged_tuple, :optional, :atom}]}},
doc:
"Map specific arguments to named inputs. Can provide any argument/attributes that the action allows."
]
]
}
@forms %Spark.Dsl.Section{
name: :forms,
describe: "Customize the definition of forms for code interfaces",
examples: [
"""
forms do
# customize the generated `form_to_create_student` function
form :create_student, args: [:school_id]
end
"""
],
entities: [
@form
]
}
use Spark.Dsl.Extension,
verifiers: [AshPhoenix.Verifiers.VerifyFormDefinitions],
transformers: [AshPhoenix.Transformers.AddFormCodeInterfaces],
sections: [@forms]
end