Ecto.Multi Update Schema with Composite PKs using Changeset without fetching the record

I ve a schema defined like follows with composite PKs

  @primary_key false
  schema "conversation_participants" do
    field :last_read_at, :utc_datetime_usec
    field :read, :boolean

    belongs_to :conversation, Conversation, primary_key: true
    belongs_to :user, User, primary_key: true
  end

  @required_fields [:user_id, :conversation_id]
  @optional_fields [:last_read_at, :read]

  def changeset(conversation, attrs) do
    conversation
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
  end

Now I tried to update the value with changeset

    conversation_data = %{
      conversation_id: conversation_id,
      user_id: sender_id
    }

    sender_conversation_changeset =
      %ConversationParticipant{}
      |> ConversationParticipant.changeset(conversation_data)
      |> Ecto.Changeset.change(%{
        last_read_at: DateTime.utc_now(),
        read: true
      })

    Multi.new()
    |> Multi.insert(:message, message_changeset)
    |> Multi.update(:conversation, sender_conversation_changeset)
    |> Repo.transaction()
    |> case do
      {:ok, %{message: message, conversation: _conversation}} ->
        {:ok, message}

      err ->
        IO.puts(inspect(err))
    end

But this gives me a Ecto.NoPrimaryKeyValueError for the user_id and conversation_id (both have primary_key: true)

My goal is to update a record in DB without loading it, but i want to validate the data before doing itI ve a schema defined like follows with composite PKs

Does the behavior change if you pass conversation_id and user_id in the ConversationParticipant?

    sender_conversation_changeset =
      %ConversationParticipant{
        conversation_id: conversation_id,
        user_id: sender_id
      }
      |> Ecto.Changeset.change(%{
        last_read_at: DateTime.utc_now(),
        read: true
      })

this may work, but ecto skips validating conversation_id and user_id

If you’re doing an update then you need to know the existing PK values and they should be in the current data part of the changeset and not the changes.

Updates and deletes are always in reference to the primary keys. Currently you are trying to say “update record that has null PK values”. You’re supposed to say “update record that has PK values X and Y”.

Changeset validations are for changes to existing data. So if you can’t trust the ids you are using it needs to be checked another way.