Clearing an `embeds_many` entry (`cast_embed/3`)

One workaround is to define an Ecto.Type. My example here isn’t well organized (the type here should be a different module, OptionList), but that’s fine for an example:

defmodule Test.Flow do
  use Ecto.Schema
  import Ecto.Changeset

  defmodule Option do
    use Ecto.Schema
    use Ecto.Type

    import Ecto.Changeset

    @primary_key false
    embedded_schema do
      field :value, :string
    end

    def changeset(opts \\ %__MODULE__{}, attrs) do
      cast(opts, attrs, [:value])
    end

    @impl Ecto.Type
    def type, do: {:array, :map}

    @impl Ecto.Type
    def cast([_ | _] = list) do
      cond do
        Enum.all?(list, &is_struct(&1, __MODULE__)) -> {:ok, list}
        Enum.all?(list, &is_map/1) -> load(list)
        true -> :error
      end
    end

    def cast(nil), do: {:ok, nil}
    def cast(_), do: :error

    @impl Ecto.Type
    def load(nil), do: {:ok, nil}

    def load(data) when is_list(data) do
      {
        :ok,
        Enum.map(data, fn entry ->
          struct!(
            __MODULE__,
            for {k, v} <- entry do
              {String.to_existing_atom(k), v}
            end
          )
        end)
      }
    end

    @impl Ecto.Type
    def dump([_ | _] = list) do
      if Enum.all?(list, &match?(%__MODULE__{}, &1)) do
        {:ok, Enum.map(list, &Map.from_struct/1)}
      else
        :error
      end
    end

    def dump(nil), do: {:ok, nil}
    def dump(_), do: :error
  end

  schema "flows" do
    field :options, Option
  end

  def changeset(flows \\ %__MODULE__{}, attrs) do
    cast(flows, attrs, [:id, :options])
  end
end

It’s a lot of work, and I suspect that some of it could be wrapped in a macro.

I fear that the lesson here is to avoid embeds_many if your column is intentionally nullable, because Ecto works against your design in this case.