Search code examples
elixirphoenix-frameworkecto

Trouble updating a record with Ecto.Multi


I have a blog app in which, when someone updates a post, it adds an entry into the newsfeed table. The Post and Newsfeed schemas are as follows:

mix phx.new.json Content Post posts title:string content:string
mix phx.new.json Content Newsfeed newsfeeds message:string

Here is the wrapper function:

  def updateContent(%{id: id, content: content}, _info) do
    post = Repo.get(post, id)
    Content.update_content_and_add_to_newsfeed(post, %{id: id, content: content})
  end

And here is the logic in the Content context:

  def update_content_and_add_to_newsfeed(post, %{id: id, content: content}) do
    multi =
      Multi.new
        |> Multi.update(:post, update_post(post, %{content: content}))
        |> Multi.insert(:newsfeed, %Newsfeed{message: "post updated"})

    case Repo.transaction(multi) do
      {:ok, %{post: post}} ->
        {:ok, post}
      {:error, _} ->
        {:error, "Error"}
    end
  end

Here is the update_post function:

  def update_post(%Post{} = post, attrs) do
    post
    |> Post.changeset(attrs)
    |> Repo.update()
  end

When I run this code, the content updates in the database, but no newsfeed item gets inserted, and I see this error message in the console:

Server: localhost:4000 (http)
Request: POST /graphiql
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Ecto.Multi.update/4

Any idea how to fix this? I'm using v2.2.6 and 1.3 with Absinthe.


Solution

  • Your Multi.update/4 call is incorrect, since it expects a changeset. Instead, you're updating your Post outside the transaction and passing it the result of your update.

    Remember, the purpose of a Transaction is to rollback in case of an error. That means if it fails, all changes should be reversed (which doesn't happen in your case).


    Delete your update_post method, and instead just pass the changeset:

    multi =
      Multi.new
        |> Multi.update(:post, Post.changeset(post, %{content: content}))
        |> Multi.insert(:newsfeed, %Newsfeed{message: "post updated"})
    

    Also, if an Ecto.Multi transaction fails, it returns a 4-element error tuple, not a regular 2-element one. So change your case statement like this:

    case Repo.transaction(multi) do
      {:ok, %{post: post}} ->
        {:ok, post}
      {:error, _op, _value, _changes} ->
        {:error, "Error"}
    end