Ecto - updating nested embed

ecto, elixir

Solution

I think you can wrap embed into `Ecto.Changeset.change/2` before use it in `Ecto.Changeset.html#put_embed/4`

Full example:

Migration:

defmodule Final.Repo.Migrations.CreateTree do
  use Ecto.Migration

  def change do
    create table(:trees) do
      add :settings, :map
    end
  end
end

Model Tree:

defmodule Final.Tree do
  use Final.Web, :model

  schema "trees" do
    embeds_one :settings, Final.TreeSettings
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [])
    |> cast_embed(:settings)
  end
end

Model TreeSettings:

defmodule Final.TreeSettings do
  use Final.Web, :model

  embedded_schema do
    field :columns, :map
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:columns])
  end
end

The test:

defmodule Final.TreeTest do
  use Final.ModelCase

  alias Final.Tree

  test "updating nested embed" do
    Repo.insert! Tree.changeset(%Tree{}, %{settings: %{columns: %{"key" => "value", "key2" => "value2"}}})

    tree = Repo.one(Tree)
    settings_changeset = tree.settings
    |> Ecto.Changeset.change(%{columns: %{tree.settings.columns | "key" => "new value"}})

    changeset = tree
    |> Ecto.Changeset.change
    |> Ecto.Changeset.put_embed(:settings, settings_changeset)
    Repo.update! changeset

    assert Repo.one(Tree).settings.columns == %{"key" => "new value", "key2" => "value2"}
  end
end

Problem

I can't update nested settings with ecto, I either get "no changes" changeset or errors. Migration: ``` def change do create table(:trees) do ... add :settings, :map ``` Settings looks like: ``` defmodule Final.TreeSettings do use Ecto.Schema embedded_schema do ... field :columns, :map timestamps end end ``` Notice the nested columns map. I can insert new Tree row easily like: ``` changeset = Tree.changeset(%Tree{}, %{user_id: user_id, name: x}) |> Ecto.Changeset.put_embed(:settings, treeSettings) ``` But updating it the same way doesn't work: ``` get_tree = Repo.one! from p in Tree, where: p.name == ^tree["name"], where: p.user_id == ^user_id settingss = get_tree.settings settingss = Kernel.update_in(settingss.columns[tree["setting"]][tree["type"]], fn x -> "asdasd" end) # IO.inspect(settingss) shows correct changes here. changeset = get_tree |> Ecto.Changeset.change |> Ecto.Changeset.put_embed(:settings, settingss) IO.inspect changeset ``` Gives: ``` #Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #Final.Tree<>, valid?: true> ```

Original source