Hi there! I’m having fun building a recipe site with phoenix, but I’m getting stuck with a dynamic form I have.
I’m following this tutorial, trying to make it so that my form can dynamically add and remove form fields. Currently, I’m trying to make it so that I can delete form fields. My problem is that I can delete a field once but after trying again I get this error:
** (RuntimeError) cannot replace related %Galley.Recipes.RecipeIngredient{id: “6n3WR”, ingredient: “”, measurement: “”, quantity: “”}. This typically happens when you are calling put_assoc/put_embed with the results of a previous put_assoc/put_embed/cast_assoc/cast_embed operation, which is not supported. You must call such operations only once per embed/assoc, in order for Ecto to track changes effeciently
The original tutorial code looks like this:
def handle_event("remove-variant", %{"remove" => remove_id}, socket) do
variants =
socket.assigns.changeset.changes.variants
|> Enum.reject(fn %{data: variant} ->
variant.temp_id == remove_id
end)
changeset =
socket.assigns.changeset
|> Ecto.Changeset.put_assoc(:variants, variants)
{:noreply, assign(socket, changeset: changeset)}
end
Mine is similar, but the form I’m trying to change is part of an embedded schema:
defmodule Galley.Recipes.Recipe do
use Ecto.Schema
import Ecto.Changeset
alias Galley.Recipes, as: R
schema "recipes" do
field :source, :string
field :title, :string
field :slug, :string
field :yields, :string
embeds_many :steps, R.RecipeStep, on_replace: :delete
embeds_many :ingredients, R.RecipeIngredient, on_replace: :delete
embeds_one :time, R.RecipeTime, on_replace: :update
timestamps()
end
defmodule Galley.Recipes.RecipeIngredient do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :ingredient, :string
field :quantity, :string
field :measurement, :string
end
def changeset(step, attrs) do
step
|> cast(attrs, [:ingredient, :quantity, :measurement])
|> validate_required([:ingredient, :quantity])
end
end
And the code that is trying to remove the deleted field:
def handle_event("remove-ingredient", %{"remove" => id_to_remove} , socket) do
IO.inspect(socket, pretty: true)
ingredients =
socket.assigns.changeset.changes.ingredients
|> Enum.reject(fn %{:data => ingredient} ->
ingredient.id == id_to_remove
end)
IO.inspect(ingredients, pretty: true)
changeset = socket.assigns.changeset |> Ecto.Changeset.put_embed(:ingredients, ingredients)
{:noreply, assign(socket, changeset: changeset)}
end
Which results in the error at the top of the post. I’m still a little confused about what might be the proper way to be working with changesets here. Does anybody have any input? I’m hoping I’m just missing a concept here due to my newness to Ecto.
Thanks for any help!