How to add or remove has_many association in LiveView form?

Hi there!

I have Order schema:

defmodule Order do
  has_many :items, Item, on_replace: :delete_if_exists

  def changeset(order, attrs) do
    order
    |> cast(attrs, attrs_list)
    |> cast_assoc(:items, with: Item.changeset/2)
  end
end

What would be the best way to add or remove items to the order form?

Currently, I have function which modifies changeset, but I don’t feel it’s the right way

def add_item_to_changeset(changeset, item) do
  items = get_field(changeset, :items)
  |> Enum.concat([Item.changeset(item)])

  changeset
  |> put_assoc(:items, items)
end
1 Like

Hi :wave:

It depends… Do you want to add many Items at once and update the whole set of Items of an Order as a whole (adding new ones and potentially removing existing items)? Then you can use use cast_assoc and put_assoc and friends for the :items relation to update the whole set of Items.

If you only want to add a single Item to an existing Order, then you can better create an Item with the order_id set correctly.

The difference between the two approaches is explained here as part of the put_assoc documentation.

If this is not enough input to get to a solution, you might want to explain how you’re using that add_item_to_changeset function (in a LiveView as part of a phx-click handler, or as a function in one of your contexts, or …), and how your UI looks like (to figure out what you’re actually doing).

1 Like
7 Likes

but in the case that have more one level of one-to-many? like lines having many items: items(name,owner)?

Applying the approaches outlined in the article should scale to nested associations/embeds as well.

2 Likes

Great post, thanks.

Awesome post, it helped me a ton.

I still ran into some little nuances though. I think it all has to do with reusing dom ids on input fields.

lines = 
  if Ecto.Changeset.change(to_delete).data.id do
    List.replace_at(existing, index, Ecto.Changeset.change(to_delete, delete: true))
  else
    rest
  end

So if the item doesn’t have a primary key, it’s removed from the list of associated things all together. This confuses the LiveView, especially around phoenix-feedback-for. For example, if I delete line number 3, then line number 4 now gets line 3’s old dom id when the form is rerendered.

I had other weird issues that I think are related to this same thing. Like deleting lines and other lines that were shifted up would not have their values properly rendered. Also input focus issues.

All these little things went away when I kept all the deleted items on the page (even ones without primary key) and just hid them instead.

2 Likes