halostatue
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.
Trending in Questions
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
Documentation
While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
Hello,
I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind.
However, when I launch mix phx.server, I get an error...
New
Hi everyone,
I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding.
I sta...
New
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
I’m working on a small exercise involving update_in/3, and I came up with this solution:
data = %{
name: "Periodic Table",
category:...
New
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication):
toke...
New
Other Trending Topics
Edit: 2026 May 15 - This post is archived.
Mob is alive!!
Main docs: mob v0.7.11 — Documentation
A bit of explanation for the slightly c...
New
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
A little off-topic, but I feel like people here have a good head on their shoulders.
I used to be quite good at making software. Was luc...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ai
- #ecto-query
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #metaprogramming
- #hex










Showing Posts 1 to 8- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
dimitarvp
I seem to remember people around here saying that
Ecto.Changesetis not treatingnilas an actual change but don’t quote me on that.But in case it’s true, what’s the difficulty in making the empty list the special empty value you’re looking for?
halostatue
Because it doesn’t seem to work. I have added the following to my
.iex.exsfor my project to explore.And this is my experimental session (I just dropped and recreated the table in
psql):But that is incorrect. If the array is empty, the field should be set to
NULL, and that’s clearly not the case:I can change the wire serialization so that an empty array is returned as
null, but it feels odd that anullablefield can no longer be set tonull— and I can’t see any way from thefetch_changeto know that this is going to be cleared. I would have to refer back toattrsto see if I should do anything.sodapopcan
This should be:
IE, don’t include the empty map as that implies you have an option you want to run through a changeset. You just want an empty list meaning “no options.” Otherwise ya, AFAIK, Ecto’s has_many relations are always going to be lists (ie, not null). There was some discussion around this that I can’t find atm.
halostatue
That initial insert was to provide a record that needed to have
[{value: null}]turned intonullthrough clearing to illustrate my problem. The later changeset calls illustrate this more clearly.My issue is with
embeds_many, nothas_many. If I were storing this data in a separate table, this would not be a problem, because the “owned” records would simply be deleted.This feels like something that can and should be handled better. Maybe with something like
embeds_many :field, Type, nullable: true, which would theoretically be able to flag tocast_embed/4that if the value isnil(and not missing), then it should be set tonilin the update.I’m going to need to figure a way around this, because an empty list is explicitly not a valid value. It’s either
nilor a non-empty list.sodapopcan
Sorry, I meant embeds_many—they work the same in that regard. And also I seem to have misread your iex output.
I found the discussion I was thinking of:
halostatue
Thanks for the link.
Yeah. I have explicitly disallowed the empty list in the type definition, which is why this behaviour is…disappointing. I may need to see if there is a way to make
embeds_manyandcast_embedwork properly for this case, because as a few posts in that thread make clear,NULLis not the same as[]and we’re just somewhat lucky that I hadn’t made a constraint on the table which checked fornullorjsonb_array_length(options) > 1(this is still a good idea, but until I can work around this issue, I can’t have it).sodapopcan
You could add your voice to that thread. Not sure if it will do much good but you can always try! My guess is that this just doesn’t come up often enough and there are other ways around it, though I’m not quite sure of the workarounds myself.
halostatue
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: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_manyif your column is intentionally nullable, because Ecto works against your design in this case.