Mi código está dando como resultado una rara inserción doble o triple en la base de datos y no sé por qué. Es muy difícil de reproducir, pero puedo mirar las marcas de tiempo para ver que el tiempo creado es básicamente el mismo cuando sucede. Creo que solo ocurre cuando CardMeta no se encuentra.
Me imagino que necesito agregar una clave única o envolverla en una transacción.
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 endEditar: agregar fuente de conjunto de cambios
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) endLlamando a enterrar desde el controlador
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) endEditar:
Noté que el campo last_seen tiene una diferencia de microsegundos entre filas duplicadas, mientras que el campo create_at no tiene esa resolución. Por lo tanto, sospecho que la llamada insert_or_update está bien, pero el controlador se dispara dos veces antes de que se actualice la base de datos. Esto podría ser algo del lado del cliente, en lo que no quiero pensar. Así que solo voy a agregar una clave única.
Como alternativa a la respuesta de @aliCna, si no desea cambiar la clave principal en CardMeta , puede poner una restricción de índice única en la base de datos con una migración:
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 endQue luego puede manejar en su conjunto de cambios para producir buenos errores si ocurren conflictos:
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) endCreo que puede resolver esto agregando una clave principal compuesta en user_id y 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 endSi esto no resuelve su problema, agregue su modelo de datos aquí.