If I am using cast_embed/3
, how do I clear an embeds_many
entry?
I have a table flows
that has a options JSONB
column
CREATE TABLE flows (
id bigint generated always as identity primary key,
options JSONB
);
I have defined the schema as follows:
defmodule Test.Flow do
use Ecto.Schema
import Ecto.Changeset
defmodule Option do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :value, :string
end
def changeset(opts \\ %__MODULE__{}, attrs) do
cast(opts, attrs, [:value])
end
end
schema "flows" do
embeds_many :options, Option, on_replace: :delete
end
def changeset(flows \\ %__MODULE__{}, attrs) do
flows
|> cast(attrs, [:id])
|> cast_embed(:options)
|> validate_length(:options, min: 1)
end
end
In Typescript type terms, the type would look something like this:
interface Flow {
id: number
options?: [Option, ...Option[]] | null
}
interface Option {
value: string
}
That is, options
should be null
or an array of at least one Option
. But I can’t make that happen:
iex(1)> {:ok, f} = Repo.insert(Test.Flow.changeset(%{})
{:ok,
%Test.Flow{
__meta__: #Ecto.Schema.Metadata<:loaded, "flows">,
id: 1,
options: []
}}
iex(2)> Test.Flow.changeset(f, %{options: nil})
#Ecto.Changeset<
action: nil,
changes: %{},
errors: [options: {"is invalid", [validation: :embed, type: {:array, :map}]}],
data: #Test.Flow<>,
valid?: false
>
iex(3)> Repo.update!(f, %{options: [%{}]})
%Test.Flow{
__meta__: #Ecto.Schema.Metadata<:loaded, "flows">,
id: 1,
options: [%Test.Flow.Option{value: nil}]
}
iex(4)> f = Repo.get(TestFlow, 1)
%Test.Flow{
__meta__: #Ecto.Schema.Metadata<:loaded, "flows">,
id: 1,
options: [%Test.Flow.Option{value: nil}]
}
iex(5)> Test.Flow.changeset(f, %{options: []})
#Ecto.Changeset<
action: nil,
changes: %{
options: [
#Ecto.Changeset<action: :replace, changes: %{}, errors: [],
data: #Test.Flow.Option<>, valid?: true>
]
},
errors: [
options: {"should have at least %{count} item(s)",
[count: 1, validation: :length, kind: :min, type: :list]}
],
data: #Test.Flow<>,
valid?: false
>
This feels like it should be possible, if not easy, but I don’t really see a way to do it, especially since there doesn’t appear to be a distinction between attrs
of %{}
(options
is missing) and %{options: nil}
(options
is explicitly nulled). That is, when I do Test.Flow.changeset(f, %{options: nil}).changes
, I get %{}
.
I suppose that I could use a sigil value (:none
), but that feels…awkward.