I’m running into an Ecto.NoPrimaryKeyValueError
when using Multi.insert_or_update
.
The function below receives a JSON payload for a Reference
record. Embedded is a products
attribute which contains a list of products that belong to the given reference. The reference may or may not exist. If the reference exists, its products may or may not exist. A product can not exist without a reference.
A Product
belongs to a Reference
and a Store
. Here’s an example payload, simplified for brevity:
%{
"brand_name" => "Philips",
"ean" => "707",
"sku" => "123",
"name" => "Screw Driver",
"products" => [%{"store_id" => 1}],
"weight_in_grams" => 1000
}
The following works only if the Product
already exists. If the product is new, I get an Ecto.NoPrimaryKeyValueError
. I’m not sure if this is a bug, or if I’m doing something wrong.
def create_or_update_reference(sku, %{"products" => product_params} = reference_params) do
changeset =
case Catalog.get_reference_by_sku(sku) do
nil ->
%Reference{}
reference ->
reference
end
|> Reference.changeset(reference_params)
Multi.new()
|> Multi.insert_or_update(:reference, changeset)
|> Multi.merge(fn %{reference: reference} ->
insert_or_update_products(reference, product_params)
end)
|> run_multi()
end
defp insert_or_update_products(%Reference{id: reference_id} = reference, product_params) do
product_params
|> Enum.map(fn %{"store_id" => store_id} = params ->
store_id
|> Stores.get_product_by_store_and_reference(reference_id)
|> case do
nil ->
%Product{store_id: store_id, reference_id: reference_id}
product ->
product
end
|> Product.changeset(params)
end)
|> Enum.map(fn changeset ->
operation = String.to_atom("product_#{unique_integer}")
Multi.update(Multi.new(), operation, changeset)
end)
|> Enum.reduce(Multi.new(), &Multi.append/2)
end
defp run_multi(multi) do
case Repo.transaction(multi) do
{:ok, result} ->
{:ok, result}
{:error, _failed_op, changeset, _changes} ->
{:error, changeset}
end
end
defp unique_integer, do: System.unique_integer([:positive])
Here’s the full error:
test create_or_update_reference/2 with valid data create_or_update_reference/2 when reference does not exist (Obramax.ReferenceRegistrarTest)
test/obramax/reference_registrar/reference_registrar_test.exs:8
** (Ecto.NoPrimaryKeyValueError) struct `%Obramax.Stores.Product{__meta__: #Ecto.Schema.Metadata<:built, "products">, id: nil, inserted_at: nil, order_management_code: nil, price: #Ecto.Association.NotLoaded<association :price is not loaded>, reference: #Ecto.Association.NotLoaded<association :reference is not loaded>, reference_id: 11, store: #Ecto.Association.NotLoaded<association :store is not loaded>, store_id: 10, updated_at: nil}` is missing primary key value
code: assert {:ok, %Reference{} = reference} = ReferenceRegistrar.create_or_update_reference(reference_params["sku"], reference_params)
stacktrace:
(ecto) lib/ecto/repo/schema.ex:763: anonymous fn/3 in Ecto.Repo.Schema.add_pk_filter!/2
(elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/repo/schema.ex:257: Ecto.Repo.Schema.do_update/4
(ecto) lib/ecto/multi.ex:421: Ecto.Multi.apply_operation/5
(elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/multi.ex:411: anonymous fn/5 in Ecto.Multi.apply_operations/5
(db_connection) lib/db_connection.ex:1374: DBConnection.transaction_nested/2
(db_connection) lib/db_connection.ex:1234: DBConnection.transaction_meter/3
(db_connection) lib/db_connection.ex:798: DBConnection.transaction/3
(ecto) lib/ecto/multi.ex:447: Ecto.Multi.apply_operation/4
(ecto) lib/ecto/multi.ex:421: Ecto.Multi.apply_operation/5
(elixir) lib/enum.ex:1899: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/multi.ex:411: anonymous fn/5 in Ecto.Multi.apply_operations/5
(ecto) lib/ecto/adapters/sql.ex:576: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
(db_connection) lib/db_connection.ex:1283: DBConnection.transaction_run/4
(db_connection) lib/db_connection.ex:1207: DBConnection.run_begin/3
(db_connection) lib/db_connection.ex:798: DBConnection.transaction/3
(ecto) lib/ecto/repo/queryable.ex:23: Ecto.Repo.Queryable.transaction/4
(obramax) lib/obramax/reference_registrar/reference_registrar.ex:77: Obramax.ReferenceRegistrar.run_multi/1
test/obramax/reference_registrar/reference_registrar_test.exs:43: (test)