Skip to content

Support unstructured data in nested forms #463

@torepettersen

Description

@torepettersen

Code of Conduct

  • I agree to follow this project's Code of Conduct

AI Policy

  • 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.

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.

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

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:

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions