Struggling trying to insert a "parent" with "children" using Ecto.Multi and changesets

I am trying to write a function that creates a transaction to insert a parent struct Parent with a bunch of childrens Children.
The input parameters are coming from a form, so everything’s a string, example:

%{
  "parent_field" => "trytry",
  "children" => %{
    "0" => %{
      "children_field" => "baebae"
    },
    "1" => %{
      "children_field" => "ciacia"
    }
  },
}

What I did so far is something like this (NOT WORKING):

def insert_parent_and_children(params) do
  parent_params = Map.delete(params, "children")
  parent_changeset = Parent.changeset(%Parent{}, parent_params)
  children_params = Map.get(params, "children") |> Map.values()

  Multi.new()
  |> Multi.insert(:parent, parent_changeset)
  |> Multi.merge(fn %{parent: parent} ->
    # Here I got the parent id, but how can I insert all the children with validation?
  end)
end

I can’t use Multi.insert_all because I would like to use the changests (I have to show errors on the UI form).

Thank you for any help

Why use Multi instead of Ecto.Changeset.cast_assoc on the parent changeset?

How can I put the parent id in the childrens with cast_assoc though?
I thought about Multi just to have that, I may be wrong.

You don’t need to. If you use associations that will be handled for you automatically.

Ok I may be doing something wrong then, because if I try:

p = %{... the params ...}
Parent.changeset(%Parent{}, p) |> Ecto.Changeset.cast_assoc(:children)

The changeset is invalid with:

#Ecto.Changeset<
  action: nil,
  changes: %{
    parent_field: "a field",
    children: [
      #Ecto.Changeset<
        action: :insert,
        changes: %{
          children_field: "a children field",
        },
        errors: [parent_id: {"can't be blank", [validation: :required]}],
        data: ....
        valid?: false
      >,
      #Ecto.Changeset<
        action: :insert,
        changes: %{
          children_field: "a children field",
        },
        errors: [parent_id: {"can't be blank", [validation: :required]}],
        data: ....
        valid?: false
      >,
    ],
  },
  errors: [],
  data: ...,
  valid?: false
>

Drop the validate_required for :parent_id. The id is not known at the time of validation, but will be set later.

I usually rely solely on null: false on the db column and assoc_constraint(changeset, :parent) to catch issues of incorrect foreign keys if they happen to be manually supplied.

4 Likes

Thank you, now I am really embarassed :slightly_smiling_face:

Working like a charm.