Code improvement and how to apply a transaction?

Good Morning,
Could help me improve the code and apply a “transaction”, if one goes wrong …
I have never worked with “transaction”, so I don’t know how to apply it in this scenario.

Below is my controller

  def create(conn, %{"client" => client_params}) do
    url = "https://test.test.com.br/v2/customers/"
    headers = %{"Content-Type" => "application/json", "Authorization" => "Basic sadad=="}
    hackney = [basic_auth: {"AUTHAPI", "AUTHAPI"}]

    client_params_strong = for {key, val} <- client_params, into: %{}, do: {String.to_atom(key), val}

    body = Poison.encode!(
      %{
        ownId: Coherence.current_user(conn).id |> to_string(),
        fullname: client_params_strong.fullname,
        email: Coherence.current_user(conn).email |> to_string(),
        birthDate: client_params_strong.birthdate,
        taxDocument: %{
          type: client_params_strong.type_taxdocument,
          number: client_params_strong.number_taxdocument
        },
        phone: %{
           countryCode: client_params_strong.countrycode_phone,
           areaCode: client_params_strong.areacode_phone,
           number: client_params_strong.number_phone
        },
        shippingAddress: %{
           city: client_params_strong.city_shippingaddress,
           district: client_params_strong.district_shippingaddress,
           street: client_params_strong.street_shippingaddress,
           streetNumber: client_params_strong.streetnumber_shippingaddress,
           zipCode: client_params_strong.zipcode_shippingaddress,
           state: client_params_strong.state_shippingaddress,
           country: client_params_strong.country_shippingaddress
        }
     }

  )

    changeset = Coherence.current_user(conn)
    |> Ecto.build_assoc(:client)
    |> Client.changeset(client_params)


    #case Structure.create_client(client_params) do
    case Repo.insert(changeset) do
      {:ok, client} ->
        conn
        |> put_flash(:info, "Cliente criado com successo!")
        |> redirect(to: Routes.client_path(conn, :show, client))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end

    case HTTPoison.post(url, body, headers, [hackney: hackney]) do
      {:ok, %HTTPoison.Response{status_code: 400}} ->
        conn
        |> put_flash(:info, "Error 400 Bad Request")
        |> redirect(to: Routes.client_path(conn, :new))

      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.inspect reason
    end

  end
1 Like

Not sure which parts of your code you want as part of the transaction but have a look at this thread:

There’s also a really good section on Transactions in the Programming Ecto (Pragprog) book :smiley:

The main thing to note is that you will want your non-DB part of the transaction to take place after the database part/s of the transaction.

3 Likes

I forgot to talk about where I want to apply this transaction, sorry.

So there is 2 insert.
1 = local database
case Repo.insert(changeset) do

2 = Api (via post)
case HTTPoison.post(url, body, headers, [hackney: hackney]) do

I want to put a transaction (rollback) in case of any error in either.

Thanks @AstonJ , I will look for more on how to do this.

1 Like

There is no transaction in API call, at least not something as a DB transaction. You could probably send a delete command if this is enough to rollback.

Maybe do API call first?

Then DB if Api call has succeed?

1 Like

As @kokolegorille said, a database transcation only covers a single database. If you want to wrap up an DB insert with a remote API call then you need to reach for a heavier weight tool. The most common one is Sage: https://github.com/Nebo15/sage

It can help you write the code to handle if either step fails (sometimes you will need to write code that “undoes” an action).

1 Like

What about Multi.run?

Multi.run

Multi allows you to run arbitrary functions as part of your transaction via run/3 and run/5 . This is especially useful when an operation depends on the value of a previous operation. For this reason, the function given as a callback to run/3 and run/5 will receive the repo as the first argument, and all changes performed by the multi so far as a map for the second argument.

The function given to run must return {:ok, value} or {:error, value} as its result. Returning an error will abort any further operations and make the whole multi fail.

https://hexdocs.pm/ecto/Ecto.Multi.html#module-run

In the Ecto book they use it to run a search engine update function after some db-related commands (all part of the transaction).

5 Likes

I’ve used Multi.run in the past for almost the same scenario as @AstonJ mentioned, with great success.

2 Likes

Thank you very much @AstonJ ,
really it will suit me …
I am studying ways to apply in my code.
Thank you

1 Like