fix: properly support lists of embeds

closes #152
This commit is contained in:
Zach Daniel 2024-05-22 00:00:29 -04:00
parent 50c87b30ef
commit afcc7ae266
6 changed files with 253 additions and 16 deletions

View file

@ -0,0 +1,21 @@
defmodule Demo.Repo.Migrations.MigrateResources7 do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:tickets) do
add(:metadatas, {:array, :map})
end
end
def down do
alter table(:tickets) do
remove(:metadatas)
end
end
end

View file

@ -0,0 +1,196 @@
{
"attributes": [
{
"default": "fragment(\"gen_random_uuid()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": true
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "subject",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "map",
"source": "metadata",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": [
"array",
"map"
],
"source": "metadatas",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "description",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "response",
"references": null,
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "\"new\"",
"size": null,
"type": "text",
"source": "status",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
},
{
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"size": null,
"type": "utc_datetime_usec",
"source": "inserted_at",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
},
{
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"size": null,
"type": "utc_datetime_usec",
"source": "updated_at",
"references": null,
"allow_nil?": false,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "reporter_id",
"references": {
"name": "tickets_reporter_id_fkey",
"table": "users",
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"on_update": null,
"primary_key?": true,
"on_delete": null,
"destination_attribute": "id",
"deferrable": false,
"match_type": null,
"match_with": null,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "representative_id",
"references": {
"name": "tickets_representative_id_fkey",
"table": "users",
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"on_update": null,
"primary_key?": true,
"on_delete": null,
"destination_attribute": "id",
"deferrable": false,
"match_type": null,
"match_with": null,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": true,
"generated?": false,
"primary_key?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "organization_id",
"references": {
"name": "tickets_organization_id_fkey",
"table": "organizations",
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"on_update": null,
"primary_key?": true,
"on_delete": null,
"destination_attribute": "id",
"deferrable": false,
"match_type": null,
"match_with": null,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": false,
"generated?": false,
"primary_key?": false
}
],
"table": "tickets",
"hash": "3DE3CF59D6C0732CC835F8ACD8D68541934549BB9086EDAC65177EDF6A62FECA",
"repo": "Elixir.Demo.Repo",
"schema": null,
"identities": [],
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true
}

View file

@ -69,7 +69,7 @@ defmodule Demo.Tickets.Ticket do
end end
create :open do create :open do
accept [:subject, :metadata] accept [:subject, :metadata, :metadatas]
primary? true primary? true
argument :representative, :map, allow_nil?: false argument :representative, :map, allow_nil?: false
argument :organization, :map, allow_nil?: false argument :organization, :map, allow_nil?: false
@ -146,6 +146,10 @@ defmodule Demo.Tickets.Ticket do
public? true public? true
end end
attribute :metadatas, {:array, Demo.Tickets.Ticket.Types.Metadata} do
public? true
end
attribute :description, :string do attribute :description, :string do
public? true public? true
end end

View file

@ -326,7 +326,7 @@ defmodule AshAdmin.CoreComponents do
<select <select
id={@id} id={@id}
name={@name} name={@name}
class="mt-2 block rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm" class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
multiple={@multiple} multiple={@multiple}
{@rest} {@rest}
> >

View file

@ -971,12 +971,11 @@ defmodule AshAdmin.Components.Resource.Form do
<label class="block text-sm font-medium text-gray-700" for={@union_type_name}> <label class="block text-sm font-medium text-gray-700" for={@union_type_name}>
Type Type
</label> </label>
<div class="w-8"> <div class="w-full">
<.input <.input
phx-change="union-type-changed" phx-change="union-type-changed"
name={@union_type_name} name={@union_type_name}
type="select" type="select"
class="w-8"
value={@actual_union_type_name} value={@actual_union_type_name}
options={@possible_types} options={@possible_types}
field={@form[:_union_type]} field={@form[:_union_type]}
@ -1015,7 +1014,7 @@ defmodule AshAdmin.Components.Resource.Form do
~H""" ~H"""
<%= cond do %> <%= cond do %>
<% match?({:array, {:array, _}}, @attribute.type) -> %> <% match?({:array, {:array, _}}, @attribute.type) -> %>
<%= render_fallback_attribute(assigns, @form, @attribute, @value, @name, @id) %> <%= render_fallback_attribute(assigns, @form, @attribute, @value, @name, @id, @union_type) %>
<% match?({:array, _}, @attribute.type) && Ash.Type.embedded_type?(@attribute.type) -> %> <% match?({:array, _}, @attribute.type) && Ash.Type.embedded_type?(@attribute.type) -> %>
<.inputs_for :let={inner_form} field={@form[@attribute.name]}> <.inputs_for :let={inner_form} field={@form[@attribute.name]}>
<.input <.input
@ -1108,7 +1107,7 @@ defmodule AshAdmin.Components.Resource.Form do
name={@name || @form.name <> "[#{@attribute.name}]"} name={@name || @form.name <> "[#{@attribute.name}]"}
/> />
<% true -> %> <% true -> %>
<%= render_fallback_attribute(assigns, @form, @attribute, @value, @name, @id) %> <%= render_fallback_attribute(assigns, @form, @attribute, @value, @name, @id, @union_type) %>
<% end %> <% end %>
""" """
end end
@ -1136,7 +1135,8 @@ defmodule AshAdmin.Components.Resource.Form do
%{type: {:array, type}} = attribute, %{type: {:array, type}} = attribute,
value, value,
name, name,
id id,
union_type
) do ) do
name = name || form.name <> "[#{attribute.name}]" name = name || form.name <> "[#{attribute.name}]"
id = id || form.id <> "_#{attribute.name}" id = id || form.id <> "_#{attribute.name}"
@ -1148,7 +1148,8 @@ defmodule AshAdmin.Components.Resource.Form do
type: type, type: type,
value: value, value: value,
name: name, name: name,
id: id id: id,
union_type: union_type || default_union_type(type, attribute.constraints[:items] || [])
) )
~H""" ~H"""
@ -1163,7 +1164,8 @@ defmodule AshAdmin.Components.Resource.Form do
@form, @form,
{:list_value, this_value}, {:list_value, this_value},
@name <> "[#{index}]", @name <> "[#{index}]",
@id <> "_#{index}" @id <> "_#{index}",
@union_type
) %> ) %>
<button <button
type="button" type="button"
@ -1183,6 +1185,7 @@ defmodule AshAdmin.Components.Resource.Form do
phx-target={@myself} phx-target={@myself}
phx-value-path={@form.name} phx-value-path={@form.name}
phx-value-field={@attribute.name} phx-value-field={@attribute.name}
phx-value-union-type={@union_type}
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center" class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
> >
<.icon name="hero-plus" class="h-4 w-4 text-gray-500" /> <.icon name="hero-plus" class="h-4 w-4 text-gray-500" />
@ -1191,7 +1194,7 @@ defmodule AshAdmin.Components.Resource.Form do
""" """
end end
defp render_fallback_attribute(assigns, form, attribute, value, name, id) do defp render_fallback_attribute(assigns, form, attribute, value, name, id, _union_type) do
casted_value = casted_value =
case value(value, form, attribute) do case value(value, form, attribute) do
%AshPhoenix.Form{resource: AshPhoenix.Form.WrappedValue} = form -> %AshPhoenix.Form{resource: AshPhoenix.Form.WrappedValue} = form ->
@ -1254,6 +1257,16 @@ defmodule AshAdmin.Components.Resource.Form do
end end
end end
defp default_union_type(Ash.Type.Union, constraints) do
constraints[:types]
|> List.wrap()
|> Enum.at(0)
|> elem(0)
|> to_string()
end
defp default_union_type(_, _), do: nil
defp non_nil_form_field(_form, []), do: nil defp non_nil_form_field(_form, []), do: nil
defp non_nil_form_field(form, [field | rest]) do defp non_nil_form_field(form, [field | rest]) do
@ -1480,13 +1493,20 @@ defmodule AshAdmin.Components.Resource.Form do
|> assign(:form, form)} |> assign(:form, form)}
end end
def handle_event("append_value", %{"path" => path, "field" => field}, socket) do def handle_event("append_value", %{"path" => path, "field" => field} = params, socket) do
to_append =
case params["union-type"] do
nil -> nil
value when value != "" -> %{"_union_type" => value}
_ -> nil
end
list = list =
AshPhoenix.Form.get_form(socket.assigns.form, path) AshPhoenix.Form.get_form(socket.assigns.form, path)
|> AshPhoenix.Form.value(String.to_existing_atom(field)) |> AshPhoenix.Form.value(String.to_existing_atom(field))
|> Kernel.||([]) |> Kernel.||([])
|> indexed_list() |> indexed_list()
|> append_to_and_map(nil) |> append_to_and_map(to_append)
params = params =
put_in_creating( put_in_creating(

View file

@ -1290,10 +1290,6 @@ select {
width: 1.5rem; width: 1.5rem;
} }
.w-8 {
width: 2rem;
}
.w-80 { .w-80 {
width: 20rem; width: 20rem;
} }