Multi.new() won't commit changes to database from an {:ok, _} Multi.run() transaction

Hello all,

So I have a Multi.new() function that works perfectly.
Then I added another |> Multi.run() pipe, which returns an {:ok, _} with the field I wanted updated correctly. Repo.transaction() will also return {:ok, _} and commit all changes from all Multi.run() lines except the one I added last.
I started to debug, and I realized there is two Multi.run() lines that work with the same struct, and only the first one in the pipe will be correctly commited to the database. I looked up on the documentation and didn’t find anything about it.
Therefore my question is, can I commit changes to the same struct twice (differente field each time), in the same Multi.new() but in differente Multi.run() lines?

Thanks in advance for any help. And just in case anyone wants to know I’ll post the function bellow, and the Multi.run() that I added, and is returning {:ok, _} from Repo.update but not being commited to the database is the last one before Repo.transaction().

defp complete_signature_and_document(signature) do
    Multi.new()
    |> Multi.run(:signature, fn _repo, _changes ->
      update_signature_completed(signature)
    end)
    |> Multi.run(:document, fn _repo, %{signature: signature} ->
      Documents.update_document_status(signature.document, "current_signature")
    end)
    |> Multi.run(:audit, fn _repo, %{signature: signature} ->
      {:ok, signature} = get_signature_with_document(signature.id)

      audit(nil, "signature.completed", {signature})
    end)
    |> Multi.run(:receipt_file, fn _repo, %{signature: signature} ->
      signature = Repo.preload(signature, [:original_file, {:signees, [:individual, :company]}])

      create_receipt_file(signature)
    end)
    |> Multi.run(:merge_file, fn _repo, %{signature: signature, receipt_file: receipt_file} ->
      signature = Repo.preload(signature, [:signing_file])

      create_merged_file(signature, receipt_file)
    end)
    |> Multi.run(:complete_signature, fn _repo,
                                         %{
                                           signature: signature,
                                           receipt_file: receipt_file,
                                           merge_file: merge_file
                                         } ->
      update_signature_complete_files(signature, receipt_file.id, merge_file.id)
    end)
    |> Multi.run(:notify_signees, fn _repo, %{signature: signature} ->
      notify_signees_completed(signature.document)
    end)
    |> Multi.run(:notify_assignee, fn _repo, %{signature: signature} ->
      notify_assignee_completed(signature.document)
    end)
    |> Multi.run(:completed_at, fn _repo, %{signature: signature} ->
      signed_date =
        Documents.get_current_document_version!(signature.document.id)
        |> Map.get(:id)
        |> AuditLogs.get_by_document_version_and_signature_completed()
        |> Map.get(:inserted_at)
        |> DateTime.truncate(:second)

      DocumentModel.update_completed_at(signature.document, signed_date)
    end)
    |> Repo.transaction(timeout: 45_000)
    |> case do
      {:error, :signature, _, _} -> {:error, :signature_not_completed}
      {:error, :document, _, _} -> {:error, :document_not_completed}
      {:error, :audit, _, _} -> {:error, :signature_event_not_inserted}
      {:error, :notify_signees, _, _} -> {:error, :email_not_sent}
      {:error, :completed_at, _, _} -> {:error, :completed_at_field_not_saved}
      {:ok, result} -> {:ok, result}
    end
  end

I am not seeing Repo.update in your code.

Can you add |> IO.inspect() at the end of the second last statements in Multi.run? Not seeing anything obviously wrong now.

Thanks for your answer.

Repo.update is in another module.

In the Multi.run(:completed_at), it returns {:ok, _} and the struct updated.
But if I put IO.inspect after Repo.transaction() it also returns me {:ok, _}, but then if I go to completed_at, the stuct won’t be updated.

So my doubt is, am I having a conflict because I’m trying to update the Document struct in two differente multi.run ? Besides the fact that it’s two differente fields

You "go to completed_at", but in which part of results?

If you are talking about the result from the Repo.transaction, in that result it won’t have updated the field I want

I meant something else. If I was working with you and thus was a newcomer to the code my first thing to do would be to add IO.inspect()-s here:

notify_assignee_completed(signature.document)
|> IO.inspect()

and

      DocumentModel.update_completed_at(signature.document, signed_date)
      |> IO.inspect()

Because you said that you see problems with these last two. But maybe I misunderstood you.

To clarify, I’m asking which part of the {:ok, %{signature: ..., document: ..., audit: ..., receipt_file: ... etc etc}} result you’re expecting to see the change in.

A general note about multi design: if you need to do more DB calls in subsequent steps to find results of previous steps (what I’m interpreting the pipeline in :completed_at as doing), consider making the previous steps return the right shape so that data’s already in-hand. For instance, can the call to audit in the :audit step return {:ok, %AuditLog{}}?

yes, you could, but you would need to be more careful on ensuring that the struct is returned after first multi due to possibility struct data change update after first multi, for example consider following code:

Multi.new()
|> Multi.run(:new_document, fn _, _ ->
  document = new_document()
end)
|> Multi.run(:complete_document, fn _, %{new_document: document} ->
  update_document(document, %{status: "COMPLETE"})
end)
|> Multi.run(:update_data_after_complete, fn _, %{new_document: document} ->
  if document.status == "COMPLETE" do
    update_document(document, %{data: ....})
  else
    {:ok, :skip}
  end
end)

the result of above code is that data won’t be committed to DB, because document on new_document still haven’t been completed yet, it would be fixed by making

Multi.run(:update_data_after_complete, fn _, %{new_document: document} ->

to

Multi.run(:update_data_after_complete, fn _, %{complete_document: document} ->

After Repo.update in the function that is updating the field I want

Thanks everyone for all the answers.

So, like I said, I’m having trouble in the :completed_at Multi.run() line. Which tries to update a struct that is also updated in an earlier Multi.run() in this function called :document
After reading some suggestions here, I tried to rewrite the pipe which was giving me trouble, and instead of using |> Multi.run(:completed_at, fn _repo, %{signature: signature} ->, I changed to: |> Multi.run(:completed_at, fn _repo, %{document: document} ->, so it will use the struct that was updated earlir in the pipe. But it’s still not updating the required field. If I put IO.inspect after Repo.update it will return me {:ok, ...} and the field I want updated. Also if I put IO.inspect after Repo.transaction() it will return {:ok, ...} and in the exactly run I’m worried it will be: completed_at: %Documents.Document{completed_at: ~U[2022-01-19 13:01:58Z], ...

The field there, is exactly the field I want updated, and in that run after Repo.transaction is being updated normally, but the change is not persisting to the database. Does anyone have any idea why?
All the other changes in this Multi.new() are persisting to the database, even the one that is also updating the same struct as that one.

I would consider setting your log level to :debug and then checking the SQL that is actually sent to the database. Look for any rollbacks or unexpected or missing calls.

3 Likes