Ecto’s put_assoc as well as cast_assoc always work with a set of associations.
When you supply a list of associations to those functions it’ll
- Create any new items (no match for their passed id or no id available)
- Update any existing (preloaded) items (matched by their id)
- Delete any existing (preloaded) items, which are no longer in the passed list
|> Ecto.Changeset.put_assoc(:contents, contents ++ post.contents)
This is therefore problematic, because you’re passing the existing contents + the ones you passed into, which might also share their primary key. That’s expected to break.
I’m wondering why you’re not using cast_assoc here, which would resolve you of the need to manually combine existing contents with the passed in contents.
def upsert_content(post, attrs) do
post = Repo.preload(post, :contents)
post
|> Ecto.Changeset.cast(%{contents: attrs})
|> Ecto.Changeset.cast_assoc(:contents)
|> Repo.update()
end






















