Live View Error With Cast Embed

Context: I’m building an Echo schema that embeds_one schema which itself contains an embeds_many field. I need to allow users to add and remove from the embeds_many field. I followed the tutorial provided in the Phoenix.Component.inputs_for/1 documentation hoping to take advantage of Ecto.Changeset.cast_embed’s :sort_param/:drop_param.

Problem: When embeds are cast, the drop param gets treated as a new embedded entity and gets passed to changeset for validation. Of course this crashes every time the form is modified as drop param is a list of indexes and not a map of user input data. The docs linked above state:

Outside the inputs_for , we render an empty mailing_list[emails_drop][] input, to ensure that all children are deleted when saving a form where the user dropped all entries. This hidden input is required whenever dropping associations.

In my case since this is an embeds_many inside of an embeds_one, my empty input looks like this patron[menu_item][modifications][mods_drop][]. When the empty form is validated the value [""] gets passed to my changeset function. When delete is pressed on a populated form, the index gets passed to the changeset function like so ["1"] (populated in mount, can’t edit form in UI as it crashes on every change).

The data passed to LiveView on change looks like so:

%{
  "_persistent_id" => "0",
  "item_name" => "",
  "modifications" => %{
    "0" => %{
      "_persistent_id" => "0",
      "comment" => ""
    },
    "mods_drop" => [""],
    "mods_sort" => ["0"]
  }
}

If mods_drop => ["3"] then ["3"] gets passed to the modifications changeset. If I comment out the hidden mods_drop input, the same problem occurs, this time on the mods_sort input. I feel I must be missing something crucial in the docs but I’m not sure what.

I realized that in the documentation snippet that I shared above, does not nest emails_drop under [emails]. Applying that to my own input name led to me changing patron[menu_item][modifications][mods_drop][] to patron[menu_item][mods_drop][]. After making that change the crashing mentioned above ceased but a new bug appeared, the UI didn’t react at all. It wasn’t until I abandoned the button based approach from the Phoenix.Component.inputs_for/1 documentation and replaced it with the label/checkbox approach outlined in this fly.io article that I managed to get results I wanted.

Edit: After getting the checkbox version working I did revisit the button version to ensure it was not my error. It never worked for me

Solution:

  • Abandon the button based approach from the inputs_for/1 docs entirely
  • Be careful when hand writing strings, especially for nested structures. Perhaps a future version of inputs_for could generate this name for me like it does for form indexes. Then I could manually apply the proper value where I need it.

How did you manage to solve this issue, especially for nested structures?
Even after following the fly.io article, I get the below error.

[debug] HANDLE EVENT "validate_form_field" in BrsWeb.FormLive.Index
  Component: BrsWeb.FormLive.FormComponent
  Parameters: %{"_target" => ["field", "fields", "fields_sort"], "field" => %{"fields" => %{"fields_sort" => ["on"]}, "label" => "", "name" => "", "type" => "panel"}}
[error] GenServer #PID<0.2705.0> terminating
** (Ecto.CastError) expected params to be a :map, got: `["on"]`

Drop and sort need to be same level as the entity they’re operating on