Match against the result of Ecto.Multi.*_all operations to avoid the transaction

For example, I have the next schema:

 @primary_key false
  schema "likes" do
    belongs_to :user, User
    belongs_to :post, Post


And I want to do the next transaction:

  def user_unlikes_post(user_id, post_id) do
    |> Multi.delete_all(:like, (from Like, where: [user_id: ^user_id, post_id: ^post_id]))
    |> Multi.update_all(:decrement_likes_count, Post.decrement_likes(post_id), [])
    |> Repo.transaction

I’d like to match the result of the delete_all and, if it is not exactly 1, stop the transaction.
I think I could do it with but I’d like to know if there is an elegant way to perform this using only “data queries”.

Thanks for your time.

I think you will have to use this in this case. In order to see the results of delete_all the query needs to actually execute, which only takes place when Repo.transaction is finally called. You will need to use run if you want to adjust an operation in your transaction based on the result of a previous operation.

1 Like

is arbitrary so is made for this exact purpose of ‘arbitrary’ checks. I suppose a .delete might bark if there a 0 or multiple records - but that is very implicit, and I haven’t checked.

Also I would ‘normalize’ your DB and make sure that duplicate records never exists in the first place. If you add a DB constraints like:

ALTER TABLE likes ADD UNIQUE (user_id, post_id)

I would even suggest you add the pair as a primary key eg primary key (user_id, post_id) and then the unique constraint is not needed.

You gave me an idea, you can access to the transaction intermediate value as first argument to the function you pass to So at the moment I made this:

  def user_unlikes_post(user_id, post_id) do
    |> Multi.delete_all(:delete_like, Like.destroy_like_query(user_id, post_id))
    |>, &check_delete_like/1)
    |> Multi.update_all(:decrement_likes_count, Post.decrement_likes(post_id), [])
    |> Repo.transaction

  defp check_delete_like(%{delete_like: {0, _}}), do: {:error, :like_not_exists}
  defp check_delete_like(%{delete_like: {1, _}}), do: {:ok, nil}

But, IMHO ecto could have a method to make this simpler:
|> Multi.delete_all_assert(:delete_like, query, 1)

But maybe it is asking too much to the Multi API… or maybe, adding support for delete methods for composed keys.

I have it, but you still can delete without creating.

At the moment, I don’t have it as a primary key and i’m using unique_index with the constraint. Ecto does not allow deleting by composed key so this won’t solve my problem. Any additional reason to add it like composed primary key? I really don’t know :slight_smile: