How to properly partially update an embedded schema without primary key

Hello all,

I am trying to understand if there is an “Ecto way” of doing the following or if I have to handle it by myself.

defmodule User do
  use Ecto.Schema

  schema "users" do
    field :email, :string

    embeds_one :profile, UserProfile, on_replace: :delete

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email])
    |> cast_embed(:profile, required: true)
    |> validate_required([:email])
  end
end

defmodule UserProfile do
  use Ecto.Schema

  @primary_key false

  embedded_schema do
    field :first_name, :string
    field :last_name, :string
  end

  @doc false
   def changeset(userProfile, attrs) do
    userProfile
    |> cast(attrs, [:first_name, :last_name)
    |> validate_required([:first_name, :last_name])
  end
end

What I am trying to do is to update the embedded schema with partial data. For example:

attrs = %{profile: %{last_name: "Foo"}}
id = 1

Repo.get!(User, id) # This will return a user with `profile` embedded and value `%{first_name: "Hello", last_name: "Bar"}`
|> User.changeset(attrs)
|> Repo.update()

The above returns changeset validation error: first_name can not be blank

Is there an Ecto approach in order for the diff calculation between what i already have in my User.profile and attrs.profile to happen automatically or do I need to do it myself? If I have to do it myself, what is the recommended way?

Thank you for your time and feel free to share any thoughts :slight_smile:

I believe you want on_replace: :update on the embeds_one instead of on_replace: :delete. docs

1 Like

I will answer my own question.

The problem was this line:

embeds_one :profile, UserProfile, on_replace: :delete

it needs to be changed to:

embeds_one :profile, UserProfile, on_replace: :update

Thanks for getting back to me. We posted at the same time :slight_smile: Indeed that was it!