bvobart

bvobart

Ecto.Changeset - How do I make validation errors in embedded schemas show up on the parent?

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?

Marked As Solved

LostKobrakai

LostKobrakai

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.

Also Liked

mathieuprog

mathieuprog

Did you look into this?

bvobart

bvobart

Hi, thanks for your quick reply! Yes, I did find PolymorphicEmbed.traverse_errors/2 and I tried it (I even examined its implementation), but it can’t iterate over errors in a child changeset that doesn’t exist :sweat_smile:

I’ve created a minimal working example in a GitHub repo here just to illustrate the point a bit better:

See lib/schema.ex and test/schema_test.exs to 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 for embeds_many).

Do you want to continue this conversation here, or should I make an issue on the PolymorphicEmbed repo and continue there?

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

Other popular topics Top

yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

We're in Beta

About us Mission Statement