brady131313

brady131313

I’m trying to create a form which has a list of nested items, each of which contain some union field. I have the following hierarchy: WorkoutTemplate has many SetGroupTemplate where SetGroupTemplate looks like the following module and both FixedReps and ClassicOverload are embedded ash resources

defmodule Weightroom.Template.SetGroupTemplate do
  @moduledoc false

  use Ash.Resource,
    domain: Weightroom.Template,
    data_layer: AshPostgres.DataLayer

  alias Weightroom.Template.Progression

  attributes do
    uuid_primary_key :id
    attribute :order, :integer, public?: true, allow_nil?: false

    attribute :progression, :union do
      public? true
      allow_nil? false

      constraints types: [
                    fixed_reps: [
                      type: Progression.FixedReps,
                      tag: :type,
                      tag_value: :fixed_reps
                    ],
                    classic_overload: [
                      type: Progression.ClassicOverload,
                      tag: :type,
                      tag_value: :classic_overload
                    ]
                  ]
    end
  end

  relationships do
    belongs_to :workout, Weightroom.Template.WorkoutTemplate do
      allow_nil? false
    end

    belongs_to :exercise, Weightroom.Journal.Exercise do
      allow_nil? false
      public? true
    end
  end

  postgres do
    table "set_group_templates"
    repo Weightroom.Repo

    references do
      reference :workout, on_delete: :delete
      reference :exercise, on_delete: :delete
    end

    custom_statements do
      statement :unique_order do
        up ~s|ALTER TABLE set_group_templates ADD CONSTRAINT set_group_templates_unique_order_index UNIQUE (workout_id, "order") DEFERRABLE INITIALLY DEFERRED|

        down "ALTER TABLE set_group_templates DROP CONSTRAINT set_group_templates_unique_order_index"
      end
    end
  end

  actions do
    defaults [:read, :destroy, create: :*]
  end

When I create the form and try to add a new SetGroupTemplate I get the following error

    template
    |> AshPhoenix.Form.for_update(:update, forms: [auto?: true])
    |> AshPhoenix.Form.add_form("form[set_groups]",
      params: %{
        "progression" => %{
          "_union_type" => "fixed_reps"
        }
      }
    )

** (RuntimeError) Got no "_union_type" parameter, and no union type had a tag & tag_value pair matching the params.

If you are adding a form, select a type using `params: %{"_union_type" => "type_name"}`, or if one
or more of your types is using a tag you can set that tag with `params: %{"tag" => "tag_value"}`.

Params:

%{}

Available types:

[
  classic_overload: [
    type: Weightroom.Template.Progression.ClassicOverload,
    constraints: [on_update: :update_on_match],
    tag: :type,
    tag_value: :classic_overload
  ],
  fixed_reps: [
    type: Weightroom.Template.Progression.FixedReps,
    constraints: [on_update: :update_on_match],
    tag: :type,
    tag_value: :fixed_reps
  ]
]

If I instead use “type” instead of “_union_type” as the key for the new progression I get the same error. Stepping through the code of ash authentication it looks like AshPhoenix.Form.Auto.determine_type/3 is called twice. The first time with the map of params and it doesn’t raise. The second time, params is empty and the function raises.

I saw there is a test for union forms here and I can’t see what I’m doing differently other than using an embedded resource for the union type instead of a simple type like string/int/etc. Any help understanding where I’m going wrong would be appreciated.

Showing Posts 1 to 10

zachdaniel

zachdaniel

Creator of Ash

Hey @brady131313 we’re actually actively working on some issues around union types. Can you try main of ash_phoenix and tell me how it goes?

brady131313

brady131313 OP

@zachdaniel using main of ash_phoenix works as expected. I can pass the type of the union using both :_union_type and :tag and it works, thank you.

Neophen

Neophen

I’m trying to follow the docs:

defmodule PageBlock do
  use Ash.Type.NewType,
    subtype_of: :union,
    constraints: [
      types: [
        hero_simple: [
          type: HeroSimple,
          tag: :type,
          tag_value: :hero_simple
        ],
        hero_media: [
          type: HeroMedia,
          tag: :type,
          tag_value: :hero_media
        ]
      ]
    ]
end

But trying to use this in a resource like so:

attribute :blocks, {:array, PageBlock}, public?: true

Throws error:

** (RuntimeError) {:array, PageBlock} is not a valid type.

Valid types include any custom types, or the following short codes (alongside the types they map to):

Didn’t want to create a new post as i think it’s part of this. I’m using main branch of ash_phoenix

      {:ash, "~> 3.0"},
      {:ash_postgres, "~> 2.0"},
      {:ash_phoenix, github: "ash-project/ash_phoenix", branch: "main"},
      {:picosat_elixir, "~> 0.2.3"},

I’ll try doing it like brady131313

Neophen

Neophen

Ok figured it out,
the

defmodule Octafest.Pages.Block do
  use Ash.Type.NewType,
    subtype_of: :union,
    constraints: [
      types: [
        hero_simple: [
          type: HeroSimple,
          tag: :type,
          tag_value: :hero_simple
        ],
        hero_media: [
          type: HeroMedia,
          tag: :type,
          tag_value: :hero_media
        ]
      ]
    ]
end

has to be in a single file by itself
Same for all the others subtypes

Neophen

Neophen

Ok running into an issue:

[debug] HANDLE EVENT "save" in OctafestWeb.PageLive.Show
  Parameters: %{"form" => %{"blocks" => %{"0" => %{"_form_type" => "create", "_persistent_id" => "0", "_touched" => "_form_type,_persistent_id,_touched,_union_type,description,navigation_type,title,type", "_union_type" => "hero_media", "description" => "Some description", "navigation_type" => "simple", "title" => "Some title", "type" => "hero_media"}}}}
[warning] Unhandled error in form submission for Octafest.Pages.Page.update_blocks

This error was unhandled because Ash.Error.Framework.MustBeAtomic does not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Framework.MustBeAtomic) Octafest.Pages.Page.update_blocks must be performed atomically, but it could not be

Reason: Unions do not support atomic updates

See https://hexdocs.pm/ash/3.0.0/update-actions.html#atomic-updates for more on atomics.

This is my actions on the page resource:

  actions do
    # Truncated...
    update :update_blocks do
      # accept content as input
      accept [:blocks]
    end
Neophen

Neophen

Also is there a way to automatically add a nested ember form:

defmodule Octafest.Pages.Block.HeroMedia do
  use Ash.Resource, data_layer: :embedded

  attributes do
    uuid_primary_key :id
    attribute :title, :string, allow_nil?: false
    attribute :description, :string
    attribute :navigation_type, :string
    attribute :image, Octafest.Pages.Components.Image
    attribute :button, Octafest.Pages.Components.Button
  end

  actions do
    defaults [
      :read,
      :destroy,
      create: [:title, :navigation_type, :description, :image, :button],
      update: [:title, :navigation_type, :description, :image, :button]
    ]
  end
end

Here is my image resource:

defmodule Octafest.Pages.Components.Image do
  use Ash.Resource, data_layer: :embedded

  attributes do
    uuid_primary_key :id
    attribute :url, :string
    attribute :alt, :string
    attribute :image_id, :string, allow_nil?: false
    attribute :modifiers, :map, default: %{}
  end

  actions do
    defaults [
      :read,
      :destroy,
      create: [
        :url,
        :alt,
        :image_id,
        :modifiers
      ],
      update: [
        :url,
        :alt,
        :image_id,
        :modifiers
      ]
    ]
  end
end

HeaderMedia is part of a union type:

defmodule Octafest.Pages.Block do
  use Ash.Type.NewType,
    subtype_of: :union,
    constraints: [
      types: [
        hero_simple: [
          type: Octafest.Pages.Block.HeroSimple,
          tag: :type,
          tag_value: :hero_simple
        ],
        hero_media: [
          type: Octafest.Pages.Block.HeroMedia,
          tag: :type,
          tag_value: :hero_media
        ]
      ]
    ]
end

the union type is used like so in the page resource:

attribute :blocks, {:array, Octafest.Pages.Block}, public?: true

Here’s how i add the block:

  @impl true
  def handle_event("add_block", %{"type" => type}, socket) do
    form =
      socket.assigns.form
      |> AshPhoenix.Form.add_form("form[blocks]",
        params: %{
          "_union_type" => type
        }
      )

    {:noreply, assign(socket, :form, form)}
  end

How would i add the nested embed image form based on type?
something like:

case type do
   :header_media -> # TODO attach the image form from the start.
zachdaniel

zachdaniel

Creator of Ash

You should be able to include nested paths when you add a form, i.e "form[foo][bar][baz]", is that what you’re looking for?

Neophen

Neophen

Kind of:
The blocks is an array
so when adding a new block how do i know to add a nested array to that specific index?

  def handle_event("add_block", %{"type" => type}, socket) do
    form =
      socket.assigns.form
      |> AshPhoenix.Form.add_form("form[blocks]",
        params: %{
          "_union_type" => type
        }
      )

    form =
      case type do
        "hero_media" ->
          form
          |> AshPhoenix.Form.add_form("form[blocks][SOME_INDEX][image]",
            params: %{
              "alt" => ""
            }
          )

        _ ->
          form
      end

How would i get the SOME_INDEX value?

zachdaniel

zachdaniel

Creator of Ash

Ah, I mean, I’d have to understand more about what you are specifically trying to do. Do you want it to be the case that any form that is an image automatically has an alt form? Or do you want the form you just added to have that nested form?

If you need the former, then you’ll have to look at the form.forms object yourself and traverse it to figure out what forms are there and do some custom processing.

For the latter, perhaps something like this?

  def handle_event("add_block", %{"type" => type}, socket) do
    form =
      socket.assigns.form
      |> AshPhoenix.Form.add_form("form[blocks]",
        params: %{
          "_union_type" => type,
          "image" => %{
             "alt" => ""
          }
        }
      )

Neophen

Neophen

Works beautifully:

  def handle_event("add_block", %{"type" => type}, socket) do
    form =
      socket.assigns.form
      |> AshPhoenix.Form.add_form("form[blocks]", params: get_params(type))

    {:noreply, assign(socket, :form, form)}
  end

  defp get_params("hero_media" = type), do: %{
      "_union_type" => type,
      "image" => %{
        "url" => "",
        "alt" => "",
        "image_id" => "",
        "modifiers" => %{}
      }
    }

  defp get_params("hero_simple" = type), do: %{ "_union_type" => type }
  defp get_params(type), do: %{ "_union_type" => type }

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews