I’ve recreated a minimal example of the problem I’m experiencing, where cast_embed
on a embeds_many
is trying to protect me from accidentally overwriting my embeds when I don’t want it to. Given the following code:
defmodule TaskList do
primary_key false
embedded_schema do
embeds_many :tasks, Task
end
def changeset(data, params \\ %{}) do
data
|> cast(params, [])
|> cast_embed(:tasks)
end
end
defmodule Task do
@primary_key false
embedded_schema do
field: title
end
def changeset(data, params \\ %{}) do
data
|> cast(params, [:title])
|> validate_required(:title)
end
end
# Below called when LiveView form submitted
def handle_event("submit", params, socket) do
# task_list already has 3 Task structs assigned to `tasks`, when loaded from DB
task_list = socket.assigns.task_list
result =
task_list
|> TaskList.changeset(params["data"])
|> Repo.update()
case result do
{:ok, task_list} ->
# redirect, etc
{:error, changeset} ->
# handle error
end
end
TaskList.changeset/2
will raise an exception because I’m trying to replace the existing 3 Task
embeds with new ones, and on_replace
defaults to :raise
.
Elsewhere in my app, I’m using embeds_one :foo, Foo, on_replace: :update
, which has exactly the semantics I want, however embeds_many
does not support this option, and none of the available options (:raise
, mark_as_invalid
, :delete
) has the correct semantics. I would just like any cast_embed(:tasks)
to “update/replace” what is there.
Right now I’m doing a dance where I replace the existing list with an empty list if I will be calling cast_embed
, but it feels like a hack, and I’m replicating this hack in multiple places, and it makes the code quite messy and hard to reason about (esp when there’s other logic that conditionally calls cast_embed
).
Any ideas how to do this simply?