bvobart
Hi, I’m using Ecto embedded schemas for input validation of a JSON API. Basically, I have a Plug.Router implementation that accepts JSON and casts the input data into an Elixir struct with Ecto:
...
post "/validate" do
payload = conn.body_params
changeset = MyApp.Document.changeset(%MyApp.Document{}, payload)
if changeset.valid? do
document = Ecto.Changeset.apply_changes(changeset)
IO.inspect(document)
send_resp(conn, 200, "Payload: #{inspect(document)}\n")
else
IO.inspect(changeset.errors)
send_resp(conn, 400, "Error 400: Bad Request: #{inspect(changeset.errors)}\n")
end
end
...
My current implementation of the Ecto schema for MyApp.Document is shown below.
As you can see, a Document embeds a bunch of children, which for simplicity’s sake is now just Text elements.
defmodule MyApp.Document do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :id, Ecto.UUID
field :type, :string
embeds_many :children, MyApp.Text
end
def changeset(document, attrs) do
document
|> cast(attrs, [:id, :type])
|> cast_embed(:children)
|> validate_required([:id, :type])
|> validate_inclusion(:type, ["https://spec.nldoc.nl/Resource/Document"])
end
end
defmodule MyApp.Text do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :id, Ecto.UUID
field :type, :string
field :text, :string
end
def changeset(text, attrs) do
text
|> cast(attrs, [:id, :type, :text])
|> validate_required([:id, :type, :text])
|> validate_inclusion(:type, ["https://spec.nldoc.nl/Resource/Text"])
end
end
The casting in my router works in the sense that if I supply a document with an invalid id or where the type is incorrect, then changeset.errors in my router contains an error message and validation fails as expected (so 400 is returned).
However, if I have a similar problem in the any of the embedded objects, then the validation does indeed fail as expected, but changeset.errors in my router is empty.
How do I ensure that any errors originating from casting / validating embedded schemas (MyApp.Text), end up on the changeset returned by the parent (MyApp.Document)? Or how else do I ensure that changeset.errors in my router contains all validation errors, even from nested embeds?
Trending in Questions
Other Trending Topics
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
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
LostKobrakai
This is expected. There were no validation failures for the fields of the document. The validation values were on the children and their nested child changesets have errors.
You can iterate the nested errors with: Ecto.Changeset — Ecto v3.12.1
bvobart
Thanks for your quick reply!
That makes sense, but I don’t quite understand yet how I can use
traverse_errorsto move the errors from the nested child changesets onto the parent’s changeset. The example shown in the docs doesn’t show how it can be used for assocs and embeds, it looks more like after-the-fact error message customisation.Could you please show me an example of its usage with embedded schemas?
i also don’t really understand where the children’s errors are stored, as I don’t see them in any of the intermediate changesets between all function calls in
MyApp.Document.changesetLostKobrakai
You generally don’t do that. The errors of children are not meant to go on the key of the parent changeset.
They’re in
parent_changeset.changes.children[*].errors. The children under changes are also changeset structs, where each has their own errors.It’s not just that. It also traverses all the nested changesets within the parent and allows you to build a nested structure of all their errors. The result is likely what you want to return instead of
changeset.errors.bvobart
Indeed, I want to build a nested structure of the errors, so that I may return to my callers that e.g.
document.children[2].children[0].textis missing or something like that.However,
traverse_errorsis typed such that only strings should be returned, so I’m not sure if it suits my use-case exactly.With this knowledge, though, I could definitely put together some function to recursively iterate through all the errors and make my nested structure that way.
Thanks! I’ll mark your answer as solution for future readers
Sadly, there is one thing preventing me from being able to solve this problem at the moment, namely that my real code makes use of
PolymorphicEmbedwhich apparently only places an{"is invalid", []}error underchangeset.errors[:children]when there’s something wrong in any of the children.I’m eager to hear from @mathieuprog, the owner of PolymorphicEmbed, what his opinion is on this issue and what kind of solution would be in order.
mathieuprog
Did you look into this?
https://github.com/mathieuprog/polymorphic_embed/blob/v5.0.0/README.md#traverse_errors2
bvobart
Hi, thanks for your quick reply! Yes, I did find
PolymorphicEmbed.traverse_errors/2and I tried it (I even examined its implementation), but it can’t iterate over errors in a child changeset that doesn’t existI’ve created a minimal working example in a GitHub repo here just to illustrate the point a bit better:
https://github.com/bvobart/polymorphic-embed-errors-mwe
See
lib/schema.exandtest/schema_test.exsto see what I mean. I’ve added some tests that show the current behaviour and the behaviour that would be desirable (and that would be more akin to what Ecto does forembeds_many).Do you want to continue this conversation here, or should I make an issue on the PolymorphicEmbed repo and continue there?