Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

172
Views
Ecto insert_or_update creating multiple inserts?

My code is resulting in a rare double or triple insert into the database and I am at a loss as to why. It is very difficult to reproduce but I can look at the timestamps to see the created at time is basically the same when it happens. I believe it only occurs when the CardMeta is not already found.

I figure I need to add a unique key or wrap it in a transaction.

  def get_or_create_meta(user, card) do
    case Repo.all(from c in CardMeta, where: c.user_id == ^user.id,
      where: c.card_id == ^card.id) do
        [] ->
          %CardMeta{}
        metas ->
          hd metas
    end   
  end

  def bury(user, card) do
    get_or_create_meta(user, card)
    |> Repo.preload([:card, :user])
    |> CardMeta.changeset(%{last_seen: DateTime.utc_now(), user_id: user.id, card_id: card.id,
      learning: false, known: false, prev_interval: 0})
    |> Repo.insert_or_update
  end

Edit: adding changeset source

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
                      :user_id, :card_id])
    |> assoc_constraint(:user)
    |> assoc_constraint(:card)
  end

Calling bury from the controller

def update(conn, %{"currentCardId" => card_id, "command" => command}) do
    # perform some update on card
    card = Repo.get!(Card,card_id)
    user = Guardian.Plug.current_resource(conn)

    case command do
      "fail" ->
        SpacedRepetition.fail(user, card)
      "learn" ->
        SpacedRepetition.learn(user, card)
      _ ->
        SpacedRepetition.bury(user, card)
    end
    sendNextCard(conn, user)
  end

Edit:

I noticed the last_seen field is microseconds different between duplicated rows, whereas the create_at field does not have that resolution. Thus I suspect the insert_or_update call is fine, but the controller is firing twice before the DB updates. This could be something on the client side, which I don't want to think about. So I am just going to add a unique key.

over 4 years ago · Santiago Trujillo
2 answers
Answer question

0

As an alternative to @aliCna's answer, if you don't want to change the primary key on CardMeta, you can put a unique index constraint in the database with a migration:

defmodule YourApp.Repo.Migrations.AddCardMetaUniqueIndex do
  use Ecto.Migration

  def change do
    create unique_index(
      :card_meta, 
      [:card_id, :user_id], 
      name: :card_meta_unique_index)
  end
end

Which you can then handle in your changeset to produce nice errors if conflicts occur:

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
                    :user_id, :card_id])
  |> assoc_constraint(:user)
  |> assoc_constraint(:card)
  |> unique_constraint(:user_id, name: :card_meta_unique_index)
end
over 4 years ago · Santiago Trujillo Report

0

I believe you can solve this by adding a composite primary key on user_id and card_id

defmodule Anything.CardMeta do
  use Anything.Web, :model

  @primary_key false
  schema "card_meta" do
    field :user_id, :integer, primary_key: true
    field :card_id, :integer, primary_key: true
    . . .

    timestamps()
  end
end

If this does't solve your problem please add your data model here!

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!