Tx (tx v0.1.1)
A simple composable transaction library that serves as an experimental alternative solution to Ecto.Multi.
This library intend to tackle the main problem with Ecto.Multi:
Ecto.Multi names are global, which requires the caller of a multi-function to know about the names used in the multi.
Composing complex Ecto.Multi can run into name collision if not being careful enough.
Link to this section Summary
Functions
Compose two transactions. Run second one only if the first one succeeds.
Combine a list of transactions [Tx.t(a)]
a single Tx.t([a])
.
Combine two transactions Tx.t(a)
and Tx.t(b)
into a single Tx.t({a, b})
.
Execute the transaction Tx.t(a)
, producing an {:ok, a}
or {:error, any()}
.
Map over the successful result of a transaction.
Create a transaction.
Return a transaction that returns a pure value.
Rollback the current transaction.
Make an transaction rollback on error.
Make a transaction (not to) rollback on exception.
The raising version of run/2
.
Run a transaction to get its result.
Convert a Tx.t(a)
into a Multi.t()
.
Link to this section Types
fn_t(a)
@type fn_t(a) :: (Ecto.Repo.t() -> {:ok, a} | {:error, any()})
t(a)
@type t(a) :: fn_t(a) | Ecto.Multi.t()
Link to this section Functions
and_then(t, f)
Compose two transactions. Run second one only if the first one succeeds.
This is the "bind" operator if you're familiar with Monad.
Example:
Tx.pure(1) |> Tx.and_then(&{:error, &1}) |> Tx.execute(Repo) == {:error, 1}
concat(list)
Combine a list of transactions [Tx.t(a)]
a single Tx.t([a])
.
Tx.concat([Tx.pure(1), Tx.pure(2)]) |> Tx.execute(Repo) => {:ok, [1, 2]}
concat(a, b)
Combine two transactions Tx.t(a)
and Tx.t(b)
into a single Tx.t({a, b})
.
Tx.concat(Tx.pure(1), Tx.pure(2)) |> Tx.execute(Repo) => {:ok, {1, 2}}
execute(tx, repo, opts \\ [])
@spec execute(t(a()), Ecto.Repo.t(), keyword()) :: {:ok, a()} | {:error, any()}
Execute the transaction Tx.t(a)
, producing an {:ok, a}
or {:error, any()}
.
Options:
rollback_on_error (Default: true): rollback transaction if the final result is an {:error, any()
rollback_on_exception (Default: true): rollback transaction if an uncaught exception arises within the transaction.
Any options Ecto.Repo.transaction/2 accepts.
map(t, f)
Map over the successful result of a transaction.
Example:
Tx.pure(1) |> Tx.map(&(&1 + 1)) |> Tx.execute(Repo) == {:ok, 2}
new(f)
Create a transaction.
This function creates a Tx.t() from a function of type
(Ecto.Repo.t() -> {:ok, a} | {:error, any()})
.
Internally, the new/1
constructor simply returns the function as
is.
Example:
t = fn repo -> {:ok, 42} end
Tx.execute(Tx.new(t), Repo) == {:ok, 42}
pure(a)
@spec pure(a()) :: t(a())
Return a transaction that returns a pure value.
Property:
Tx.execute(Tx.pure(x), Repo) == {:ok, x}
rollback(repo, error)
@spec rollback(Ecto.Repo.t(), any()) :: no_return()
Rollback the current transaction.
Example:
Tx.new(fn repo ->
if 1 == 1 do
Tx.rollback(repo, "One cannot be equal to one")
else
:fine
end
end)
|> Tx.execute(Repo)
returns {:error, "One cannot be equal to one"}
immediately, with
all previous uncommited changes rolled back.
rollback_on_error(tx, rollback? \\ true)
Make an transaction rollback on error.
You can use this function to fine-tune the rollback behaviour on specific sub-transactions.
Please note that it does not support disabling rollback_on_error for sub-transactions.
rollback_on_exception(tx, rollback? \\ true)
Make a transaction (not to) rollback on exception.
Wrap around tx
with rollback_on_exception(tx, false)
if
you avoid tx
to rollback on exception. When an exception occurs,
you will get an {:error, exception}
instead.
Please note that it does not support enabling rollback_on_exception for sub-transactions because that's the default behaviour.
run!(repo, tx)
@spec run!(Ecto.Repo.t(), t(a())) :: a()
The raising version of run/2
.
This function should be rarely needed if you use the Tx.Macro.tx
macro.
run(repo, tx)
@spec run(Ecto.Repo.t(), t(a()) | any()) :: {:ok, a()} | {:error, any()}
Run a transaction to get its result.
This is a generic adapter for extracting a {:ok, a} | {:error, any()}
from
a transaction when given a repo.
- For normal transaction
tx
as a function (default), it simply call tx.(repo) - For Ecto.Multi, it creates a sub-transaction to execute it
- For a non-transactional value, it simply returns the value
This function should be rarely needed if you use the Tx.Macro.tx
macro.
to_multi(tx, name)
@spec to_multi(t(a()), any()) :: Ecto.Multi.t()
Convert a Tx.t(a)
into a Multi.t()
.
You can refer to the transactin's result ({:ok, a} | {:error, any}
) by name
.
Ecto.transaction(to_multi(pure(42), :foo)) => {:ok, %{foo: {:ok, 42}}}