0
votes

I have an Ecto schema setup that has an has_many association. I want to be able to dynamically add/remove associations to it, keeping the initial association.

I've tried to use Ecto.Changeset.put_assoc/4 and that works when loading the initial association, however each subsequent call will override the initial association.

change_one = Changeset.put_assoc(changeset, :foo_assocs, [%{index: 1}])
...
foo_assocs: [
  #Ecto.Changeset<
    action: :insert,
    changes: %{index: 1},
    errors: [],
    data: #Linker.CustomForms.FooAssoc<>,
    valid?: true
  >
]
...

Then if I call it again with another associated record to add:

change_two = Changeset.put_assoc(changeset_one, :foo_assocs, [%{index: 2}])
...
foo_assocs: [
  #Ecto.Changeset<
    action: :insert,
    changes: %{index: 2},
    errors: [],
    data: #Linker.CustomForms.FooAssoc<>,
    valid?: true
  >
]
...

My first record is overwritten.

1

1 Answers

1
votes

That is intended behavior of put_assoc\4 as it is intended to work with full data set. Actually your question is described very well in Ecto docs: https://hexdocs.pm/ecto/Ecto.Changeset.html#put_assoc/4-example-adding-a-comment-to-a-post

A map or keyword list can be given to update the associated data as long as they have matching primary keys. For example, put_assoc(changeset, :comments, [%{id: 1, title: "changed"}]) will locate the comment with :id of 1 and update its title. If no comment with such id exists, one is created on the fly. Since only a single comment was given, any other associated comment will be replaced.

So you can merge existing data with new data and use put_assoc\4 or you could work on your single association like following example where you set the association from your child

changeset = %__MODULE__{} |> has_many(:foo_assoc, [%{index: 1}])

%FooChild{index: 2}
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:parent, changeset)
|> Repo.insert!()

But I would recommend reading the above link for more detailed description of how to use put_assoc/4 and has_many.