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

View file

@ -326,7 +326,7 @@ defmodule AshAdmin.CoreComponents do
<select
id={@id}
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}
{@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}>
Type
</label>
<div class="w-8">
<div class="w-full">
<.input
phx-change="union-type-changed"
name={@union_type_name}
type="select"
class="w-8"
value={@actual_union_type_name}
options={@possible_types}
field={@form[:_union_type]}
@ -1015,7 +1014,7 @@ defmodule AshAdmin.Components.Resource.Form do
~H"""
<%= cond do %>
<% 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) -> %>
<.inputs_for :let={inner_form} field={@form[@attribute.name]}>
<.input
@ -1108,7 +1107,7 @@ defmodule AshAdmin.Components.Resource.Form do
name={@name || @form.name <> "[#{@attribute.name}]"}
/>
<% true -> %>
<%= render_fallback_attribute(assigns, @form, @attribute, @value, @name, @id) %>
<%= render_fallback_attribute(assigns, @form, @attribute, @value, @name, @id, @union_type) %>
<% end %>
"""
end
@ -1136,7 +1135,8 @@ defmodule AshAdmin.Components.Resource.Form do
%{type: {:array, type}} = attribute,
value,
name,
id
id,
union_type
) do
name = name || form.name <> "[#{attribute.name}]"
id = id || form.id <> "_#{attribute.name}"
@ -1148,7 +1148,8 @@ defmodule AshAdmin.Components.Resource.Form do
type: type,
value: value,
name: name,
id: id
id: id,
union_type: union_type || default_union_type(type, attribute.constraints[:items] || [])
)
~H"""
@ -1163,7 +1164,8 @@ defmodule AshAdmin.Components.Resource.Form do
@form,
{:list_value, this_value},
@name <> "[#{index}]",
@id <> "_#{index}"
@id <> "_#{index}",
@union_type
) %>
<button
type="button"
@ -1183,6 +1185,7 @@ defmodule AshAdmin.Components.Resource.Form do
phx-target={@myself}
phx-value-path={@form.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"
>
<.icon name="hero-plus" class="h-4 w-4 text-gray-500" />
@ -1191,7 +1194,7 @@ defmodule AshAdmin.Components.Resource.Form do
"""
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 =
case value(value, form, attribute) do
%AshPhoenix.Form{resource: AshPhoenix.Form.WrappedValue} = form ->
@ -1254,6 +1257,16 @@ defmodule AshAdmin.Components.Resource.Form do
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, [field | rest]) do
@ -1480,13 +1493,20 @@ defmodule AshAdmin.Components.Resource.Form do
|> assign(:form, form)}
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 =
AshPhoenix.Form.get_form(socket.assigns.form, path)
|> AshPhoenix.Form.value(String.to_existing_atom(field))
|> Kernel.||([])
|> indexed_list()
|> append_to_and_map(nil)
|> append_to_and_map(to_append)
params =
put_in_creating(

View file

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