Updating an Embedded Schema

Say I have a schema (which maps data from a db) with an embeds_many field:

defmodule Order do
  use Ecto.Schema

  schema "orders" do
    embeds_many :items, Item
  end
end

defmodule Item do
  use Ecto.Schema

  @primary_key {:id, :string, autogenerate: false}
  embedded_schema do
    field :title
  end
end

Periodically, data will come in from an external source where I’d want to add new items to an existing order. The items coming from the external source may or may not already be in the existing order.

Is there a standard way to do something like this? Maybe utilizing the primary key on my embedded schema?

Thanks

1 Like

Deduplication is not hard but you’re going to have to give a bit more detailed example for us to be able to suggest a solution.

Thanks @dimitarvp. So here is a situation that might happen:

  1. My orders table has an entry with order_id = 1 and items = [{"item_id": 1, "title": "a"}]. Each entry in the items array is defined by an embedded schema in my app.
  2. My app receives external data {"order_id": 1, "item_id": 2, "title": "b"} . Since my entry for order_id = 1 doesn’t have item_id = 2 yet, I want to add %Item{"item_id": 2, "title": "b"} to its items array. Is there any way for me to do this without pulling the existing database entry into my application?
  3. My app receives external data {"order_id": 1, "item_id": 1, "title": "a"}. Since my entry for order_id = 1 already has item_id = 1, I don’t want to save this to the database. Is there any way for me to check for this without pulling the existing database entry into my application?

AFAIK, it’s not possible without loading the existing record.
Check out Upserts if you haven’t already. It might give you some ideas. It might not work exactly with embedded schemas though. I’m assuming you have items as maps since they might contain arbitrary keys/values or you’re not able to modify it (working with existing/legacy code, etc). But if that’s not the case, it might be much easier to have 2 separate tables with defined columns. Then you’d do something like this:

Repo.insert!(
  %Item{order_id: 1, item_id: 1, title: "a"},
  on_conflict: :nothing
)
1 Like

This is not possible with an embedded schema. You can just load the main record + the embedded records, calculate the proper diff and then put the new collection back with Ecto.Changeset.put_assoc.

3 Likes