Updating an embedded_schema - {:array, :map}

I’m trying to update a list of maps that are stored as an embedded_schema.

Here is my setup:

ScoreTable.ex

defmodule Statcasters.ScoreTable do
  schema "score_tables" do
    embeds_one(:table_details, TableDetails)
  end
end

TableDetails.ex

defmodule Statcasters.TableDetails do
  embedded_schema do
    field(:information, {:array, :map})
  end
end

I’m trying to update an existing table_details.information array.

Failed Attempt:

def update_score_table(score_table, season_team, score) do
  details_changeset = score_table.table_details
    |> Ecto.Changeset.change(%{information: [%{key: "value"}]})

  changeset = score_table
    |> Ecto.Changeset.change
    |> Ecto.Changeset.put_embed(:table_details, details_changeset)

  Repo.update!(changeset)
end

Error:

 ** (RuntimeError) you are attempting to change relation :table_details of
     Statcasters.ScoreTable but the `:on_replace` option of
     this relation is set to `:raise`.

     By default it is not possible to replace or delete embeds and
     associations during `cast`. Therefore Ecto requires all existing
     data to be given on update. Failing to do so results in this
     error message.

     If you want to replace data or automatically delete any data
     not sent to `cast`, please set the appropriate `:on_replace`
     option when defining the relation. The docs for `Ecto.Changeset`
     covers the supported options in the "Associations, embeds and on
     replace" section.

     However, if you don't want to allow data to be replaced or
     deleted, only updated, make sure that:

       * If you are attempting to update an existing entry, you
         are including the entry primary key (ID) in the data.

       * If you have a relationship with many children, at least
         the same N children must be given on update.

I’ve tried adding on_replace: :update to the embeds_one but that doesn’t work either.

If anyone can help me update the map with the array/list for the TableDetails module I’d greatly appreciate the help

Here’s what worked for me - it’s slightly different to your situation as I modelled the list as an embeds_many, but may give you some pointers:

defmodule TableDetail do
  import Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field :info1, :string
    field :info2, :integer
  end

  def changeset(details, attrs) do
    details
    |> cast(attrs, [:info1, :info2])
    |> validate_required([:info1, :info2]
  end
end

defmodule Table do
  use Ecto.Schema
  import Ecto.Changeset

  alias TableDetail

  schema "score_table" do
    field, :wot_evs, :string
    embeds_many :details, TableDetail, on_replace: :delete
  end

  def changeset(table, attrs) do
    table
    |> cast(atrs, [:wot_evs])
    |> cast_embed(:details)
  end
end
2 Likes

You have an embedded schema and Ecto needs some hint to understand what to do with the data. The error says that you need to put on_replace: option which are

  • :update - requires a primary key to identify records
  • :delete - full replacement of the embedded schema
  • :mark_as_invalid - no crash. Only error that it is not allowed to edit this field

More about this here.

If you go with :update then provide the primary key as here says otherwise use :delete which simply replaces whole embedded structure with the new data.

5 Likes