ash-project / ash-project/ash_phoenix

Support unstructured data in nested forms

Open
#463 3 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Elixir
Stars
164
Forks
102
Avg merge
12h 21m
Merged PRs (30d)
2

Description

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

### AI Policy

- [x] I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.

### Is your feature request related to a problem? Please describe.

Hi and thanks for your work on the Ash ecosystem ❤️

I am trying to create a form with unstructured nested data.

It is probably easiest explained with an example. I have a print where the fields that I should expect is defined in a template.

```elixir
defmodule MyApp.Template do
attributes do
attribute :fields, {:array, :string}
end
end

defmodule MyApp.Print do
use Ash.Resource, domain: MyApp

attributes do
attribute :params, :map
end

actions do
create :create do
argument :template, MyApp.Template, allow_nil?: false
argument :params, :map

change fn changeset, _ ->
template = Ash.Changeset.get_argument(changeset, :template)
params = Ash.Changeset.get_argument(changeset, :params) || %{}

changeset = Ash.Changeset.change_attribute(changeset, :params, params)

Enum.reduce(template.fields, changeset, fn field, changeset ->
if params[to_string(field)] in [nil, ""] do
Ash.Changeset.add_error(changeset, field: field, message: "is required", path: [:params, field])
else
changeset
end
end)
end
end
end
end
```

This code should work perfectly well, but now I am not able to find any way to make a form that would expect this data. The form should look something like this, but I don't see any way to setup a form with for_create.

```heex
<.inputs_for :let={params} field={@form[:params]}>
<%= for field <- @template.fields do %>
<.input field={params[field]} label={to_string(field)} />
<% end %>

```

### Describe the solution you'd like

I vibe coded a solution that seems to work. I allowed for an API like this for creating the form:

```elixir
form = AshPhoenix.Form.for_create(MyApp.Print, :create,
domain: MyApp,
forms: [params: [map?: true]]
)
```

Another alternative could be for the resource to be a map:
```elixir
form = AshPhoenix.Form.for_create(MyApp.Print, :create,
domain: MyApp,
forms: [params: [resource: :map]]
)
```

I would also be super interested in your thoughts on alternative solutions.

### Describe alternatives you've considered

_No response_

### Additional context

I would be happy to create a PR with my current solution (of course after having cleaned up and reviewed the AI slop) or create a different solution, if you have any better ideas.

Contributor guide

Open the contributing guide

Research direction

Start with the AshPhoenix.Form.for_create call and the HEEx inputs_for example in the issue, then trace how the params map is represented and submitted. Compare the proposed forms: [map?: true] and [resource: :map]. Done means a form can render, submit, and validate the unstructured nested params with dynamic fields from the template.

Written by the indexing model from the issue text.

Assessment

Tech stack
elixir
Domain
frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.