I have an ecto schema with an embeds_many association like this:
defmodule Foo do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "foo" do
field :name, :string
embeds_many :addresses, Address do
field :city, :string
field :country, :string
field :house_number, :string
field :street, :string
field :zip, :integer
def changeset(address, attrs) do
address
|> cast(attrs, [:city, :zip, :street, :country, :house_number, :coordinates, :description])
|> validate_required([:city, :zip, :street, :country])
end
end
timestamps()
end
@doc false
def changeset(foo, attrs) do
foo
|> cast(attrs, [:name])
|> cast_embed(:addresses)
end
end
Now I am struggling to manipulate the associated address embedding from user data. I was able to add the first entry and can also update this one because the id is present, but I can’t find a way to insert multiple addresses because I am fighting with this error:
** (RuntimeError) you are attempting to change relation :addresses of
Foo but the `:on_replace` option of this relation
is set to `:raise`.
By default it is not possible to replace or delete embeds and
associations during `cast`. Therefore Ecto requires the parameters
given to `cast` to have IDs matching the data currently associated
to Foo. Failing to do so results in this error message.
If you want to replace data or automatically delete any data
not sent to `cast`, please set the appropriate `:on_replace`
option when defining the relation. The docs for `Ecto.Changeset`
covers the supported options in the "Associations, embeds and on
replace" section.
However, if you don't want to allow data to be replaced or
deleted, only updated, make sure that:
* If you are attempting to update an existing entry, you
are including the entry primary key (ID) in the data.
* If you have a relationship with many children, all children
must be given on update.
I think I understand the error and have also read the documentation how ecto handles these casts here: Ecto.Changeset — Ecto v3.12.5
As I understand it, ecto needs not only the new values that I want to insert but also all the children of the association on the attrs side, to determine which changes occurred and what should be inserted/updated.
But I can’t figure out a way to provide the user data map together with the already present addresses to satisfy the requirement from ecto.
If I use Foo.update_foo(foo, %{"addresses" => [address_params]})
it fails with the above error. If I try to prepend the value like so Foo.update_foo(foo, %{"addresses" => [address_params | foo.addresses]})
it fails with ** (Ecto.CastError) expected params to be a :map
because I mix structs with the user provided maps.
What is the solution to add an element to the association?