Tx (tx v0.3.0)
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 the second one only if the first one succeeds.
Combine a list of transactions [Tx.t(a)]
into a single Tx.t([a])
.
Combine two transactions Tx.t(a)
and Tx.t(b)
into a single Tx.t({a, b})
.
Avoid a transaction to rollback on exception.
Make an transaction rollback on error.
Execute the transaction Tx.t(a)
, producing an {:ok, a}
or {:error, any}
.
Map over the successful result of a transaction.
Create a transaction.
Create a transaction that returns a pure value.
Rollback the current transaction.
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() | nil | [t(any())]
You can put a value of any Tx type on the right of <-
notation in Tx.Macro.
A Tx type is any of the following:
A Tx function :: any function that takes in a repo and returns a
{:ok, a} | {:error, any()}
pair. You can create a Tx function via thetx
macro, or any tx combinators likeTx.new
,Tx.pure
,Tx.concat
.An Ecto.Multi.t() :: equivalent a tx function that returns
{:ok, %{multi_name => value}}
or{:error, multi_error}
nil :: the same was as
Tx.pure(nil)
, which always returns{:ok, nil}
when been run.A list of any Tx type :: the same as
Tx.concat(list_of_tx)
, which returns{:ok, list_of_results}
only if alllist_of_tx
succeeds.
Link to this section Functions
and_then(t, f)
Compose two transactions. Run the second one only if the first one succeeds.
This is the "bind" operation if you're familiar with Monad.
Example:
iex> Tx.pure(1) |> Tx.and_then(&{:error, &1}) |> Tx.execute(Repo)
{:error, 1}
concat(list)
Combine a list of transactions [Tx.t(a)]
into a single Tx.t([a])
.
iex> 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})
.
iex> Tx.concat(Tx.pure(1), Tx.pure(2)) |> Tx.execute(Repo)
{:ok, {1, 2}}
disable_rollback_on_exception(tx)
Avoid a transaction 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.
enable_rollback_on_error(trans)
Make an transaction rollback on error.
You can use this function to fine-tune the rollback behaviour on specific sub-transactions.
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:
iex> 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:
iex> t = fn _repo -> {:ok, 42} end
iex> Tx.execute(Tx.new(t), Repo)
{:ok, 42}
pure(a)
@spec pure(a()) :: t(a())
Create a transaction that returns a pure value.
This is the "pure"/"return" operation if you're familiar with Monad.
Executing Tx with a pure value always returns the value.
pure(a)
is equivalent to new(fn _ -> {:ok, a} end)
.
iex> Tx.execute(Tx.pure(42), Repo)
{:ok, 42}
rollback(repo, error)
@spec rollback(Ecto.Repo.t(), any()) :: no_return()
Rollback the current transaction.
Example:
iex> Tx.new(fn repo ->
...> if 1 == 1 do
...> Tx.rollback(repo, "One cannot be equal to one")
...> else
...> {:ok, :fine}
...> end
...> end)
...> |> Tx.execute(Repo)
{:error, "One cannot be equal to one"}
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, multi)
@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. You can simply use a <- t
syntax instead of a = Tx.run(repo, t)
within the tx
macro.
This function is meant to be used within a tx block or inside a Tx
closure. If you want to get a value out of a Tx, you may want to
call execute/2
instead.
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
.
EctoRepo.transaction(to_multi(pure(42), :foo)) => {:ok, %{foo: {:ok, 42}}}