"Programs must be written for people to read, and only incidentally for machines to execute." ― Abelson & Sussman

With statement in Elixir

The with statement in Elixir is a compelling way to chain together statements that depend on each other. However, I was confused on how to respond differently, depending on which statement went wrong. Luckily I came across Elixir’s with statement is fantastic article which showed me the way.

Each chained function call returns a tuple with an atom that helps us know exactly where things went wrong in the chain.

Now we are able to chain statements and in the else clause deal with the specific statement which did not respond as we would have liked it to. In the example below we will first try to look up an user, create a customer from them, add a subcription and subsequently update the user.

def create_subscription(email, plan_id, payment_method_id) do
  with {:user_lookup, %User{customer_id: nil, name: name} = user} <-
         {:user_lookup, Repo.get_by(User, email: email)},
       {:customer_creation, {:ok, %Stripe.Customer{id: customer_id}}} <-
            name: name,
            email: email,
            payment_method: payment_method_id,
            invoice_settings: %{
              default_payment_method: payment_method_id
       {:subscription_creation, {:ok, %Stripe.Subscription{id: subscription_id}}} <-
          Stripe.Subscription.create(%{customer: customer_id, items: [%{plan: plan_id}]})},
       {:user_update, {:ok, %User{}}} <-
          User.update(user, %{customer_id: customer_id, subscription_id: subscription_id})} do
    {:ok, :subscription_created}
    {:user_lookup, _} ->
      {:error, :user_lookup_error}

    {:customer_creation, _} ->
      {:error, :customer_creation_error}

    {:subscription_creation, _} ->
      {:error, :subscription_creation_error}

    {:user_update, _} ->
      {:error, :user_update_error}

    err ->
      {:error, err}