Nested cast_assoc calls not taking into account super parent id

Hello.

I have a Receipt schema which has_many Items.
An Item also has_many :subitems which is the same Item schema.

The top level of items work as expected when using cast_assoc(:items) in the Receipt changeset as ecto is inserting the receipt_id into the correct field.
However, for sub-items, the receipt_id is empty since (I’m assuming) the sub-items are coming from the Item.changeset instead of the Receipt.changeset.

The input is like this:

{
  "receipt": {
    "items": [
      {
        "title": "Best Coffee",
        "subitems": [ {"title": "Hazelnut syrup"} ],
      }
    ]
  }
}

Receipt (related code)

def new_changeset(receipt, attrs) do
    receipt
    |> cast(attrs, available_attributes())
    |> cast_assoc(:items, with: &tem.new_changeset/2)
    |> shared_validations()
end

Item (related code)

def new_changeset(item, attrs) do
    item
    |> cast(attrs, available_attributes())
    |> shared_validations()
    |> cast_assoc(:subitems, with: &Item.new_changeset/2)
end

The parent item gets the receipt_id field set correctly, while the sub-item has null in the receipt_id.
Any guidance is highly appreciated.

EDIT1
Can this be solved with cast_assoc at all or should I use something different like Ecto.Multi?

I would personally use Ecto.Multi there, or even just normal non-transactional calls unless there is a change of an item being inserted failing and the receipt should go too if that’s the case.

1 Like