Is there any better / easier way to update deeply nested embedded schema in Ecto changeset without needing to build changeset for every nested level?

Hi everyone,

I’m working with Ecto and have a schema that includes several levels of embeds_one and embeds_many. Here’s a simplified example to illustrate the structure:

defmodule MyApp.Foo do
  use Ecto.Schema
  import Ecto.Changeset

  schema "foos" do
    embeds_one :bar, Bar do
      embeds_one :baz, Baz do
        embeds_many :items, Item, on_replace: :delete do
          field :index, :integer
          field :value, :string
        end

        field :target_index, :integer
      end
    end

    timestamps()
  end
end

Now I need to update just one item inside the items list, which is inside baz, which is inside bar.

The item I want to update is the one where index == target_index, and I only want to change its value. Here’s the verbose way I’m doing it:

foo
|> change()
|> put_embed(
  :bar,
  foo.bar
  |> change()
  |> put_embed(
    :baz,
    foo.bar.baz
    |> change()
    |> put_embed(
      :items,
      foo.bar.baz.items
      |> Enum.map(fn
        %{index: i} = item when i == foo.bar.baz.target_index ->
          item |> change() |> put_change(:value, new_value)
        item -> item
      end)
    )
  )
)
|> Repo.update()

This works, but as you can see, it becomes deeply nested and hard to read/maintain, especially when dealing with many levels.

Is there a cleaner or more idiomatic way to update a deeply nested embedded field in Ecto without having to manually call change/2 and put_embed/3 at every level?

Check out ecto_nested_changeset.

2 Likes

I might not see the full picture, but why update the item through foo/bar/baz/items instead of a simple Repo.update(item_changeset)?

If it’s embedded, you can’t actually update that single item, you’ve gotta update the actual record at the top in the database with an updated inner JSON content.

1 Like

Doh, makes sense. I was thinking of assocs.

Oh crap, so was I.

Thank you! This is exactly the kind of utility I was looking for. Really appreciate it.

1 Like

Honestly this is my only “big” problem with Ecto and wish something like this shipped with it. Of course this lib works! (@)woylie has some other really nice libraries for working with Ecto and Phoenix. Not affiliated, just a fan :slightly_smiling_face:

1 Like