lode/multi
Ecto.Multi — compose several named operations into one transaction.
Each step runs in order inside the repo’s transaction, with access to the
results of earlier steps (keyed by name). If any step fails, the transaction
rolls back and transaction returns the failing step’s name and error.
Results are heterogeneous, so they’re stored as Dynamic and recovered with
the typed get helper (the value round-trips unchanged, so the coerce is
safe).
Beyond the changeset steps (insert/update/delete) and run, there
are bulk steps (insert_all/update_all/delete_all — the stored result
is the affected count), query steps (one/all/exists), and put for
injecting a precomputed value. Multis compose with merge (build steps
from earlier results), append, and prepend. Step names must be unique
across the whole composed multi: transaction rejects a duplicate with
Error(MultiFailure(..)) before running any step (a duplicate introduced
by merge fails at the merge point and rolls back). Anything not covered
by a named step — returning-variants, expression update_all_sets,
changes-dependent queries — goes through a run step, which receives the
transaction-scoped repo.
multi.new() |> multi.insert(“user”, user_schema, user_changeset) |> multi.run(“welcome”, fn(repo, changes) { let assert Ok(user) = multi.get(changes, “user”) send_welcome(repo, user) }) |> multi.transaction(repo)
Types
Accumulated results of completed steps, keyed by step name.
pub type Changes =
dict.Dict(String, dynamic.Dynamic)
A failed multi: the step that failed and why.
pub type MultiFailure {
MultiFailure(name: String, reason: error.LodeError)
}
Constructors
-
MultiFailure(name: String, reason: error.LodeError)
Values
pub fn all(
multi m: Multi,
name name: String,
schema s: schema.Schema(row),
query q: query.Query,
) -> Multi
Load every row of a query under name (Ecto’s Multi.all, via
repo.all). The stored result is a List(row).
pub fn append(multi m: Multi, other other: Multi) -> Multi
Run other’s steps after multi’s (Ecto’s Multi.append). Duplicate
step names across the two are rejected by transaction.
pub fn delete(
multi m: Multi,
name name: String,
schema s: schema.Schema(row),
row row: row,
) -> Multi
Delete a struct under name.
pub fn delete_all(
multi m: Multi,
name name: String,
query q: query.Query,
) -> Multi
Bulk-delete rows matching query under name (Ecto’s Multi.delete_all,
via repo.delete_all). The stored result is the affected count (Int).
pub fn exists(
multi m: Multi,
name name: String,
query q: query.Query,
) -> Multi
Store whether any row matches query under name (Ecto’s
Multi.exists?, via repo.exists). The stored result is a Bool.
pub fn get(
changes changes: dict.Dict(String, dynamic.Dynamic),
name name: String,
) -> Result(a, Nil)
Recover a typed result for a named step.
pub fn insert(
multi m: Multi,
name name: String,
schema s: schema.Schema(row),
changeset cs: changeset.Changeset(row),
) -> Multi
Insert a changeset under name.
pub fn insert_all(
multi m: Multi,
name name: String,
schema s: schema.Schema(row),
rows rows: List(row),
) -> Multi
Bulk-insert typed rows under name (Ecto’s Multi.insert_all, via
repo.insert_all). The stored result is the inserted count (Int).
pub fn merge(
multi m: Multi,
with build: fn(dict.Dict(String, dynamic.Dynamic)) -> Multi,
) -> Multi
Merge a dynamically-built multi (Ecto’s Multi.merge): at execution time
build receives the results so far and its multi’s steps run at this
position, before any remaining steps. A merged step name that collides
with any other step in the composed multi fails the transaction at the
merge point (and rolls back).
pub fn one(
multi m: Multi,
name name: String,
schema s: schema.Schema(row),
query q: query.Query,
) -> Multi
Fetch at most one row under name (Ecto’s Multi.one, via repo.one).
The stored result is an Option(row).
pub fn prepend(multi m: Multi, other other: Multi) -> Multi
Run other’s steps before multi’s (Ecto’s Multi.prepend). Duplicate
step names across the two are rejected by transaction.
pub fn put(
multi m: Multi,
name name: String,
value value: a,
) -> Multi
Store a precomputed value under name (Ecto’s Multi.put) — no repo
access, cannot fail. Useful for injecting parameters later steps read via
get.
pub fn run(
multi m: Multi,
name name: String,
with f: fn(repo.Repo, dict.Dict(String, dynamic.Dynamic)) -> Result(
a,
error.LodeError,
),
) -> Multi
Run an arbitrary function with access to the repo and prior results.
pub fn transaction(
multi m: Multi,
repo r: repo.Repo,
) -> Result(dict.Dict(String, dynamic.Dynamic), MultiFailure)
Run all steps in one transaction. On success, returns the accumulated results; on failure, the failing step’s name and error (and rolls back).
Step names must be unique across the whole composed multi (including
append/prepended and merged steps): a duplicate returns
Error(MultiFailure(name: <duplicate>, reason: QueryError(..))) — before
any step runs for statically-known names, at the merge point (rolling
back) for names a merge introduces. DIVERGENCE: Ecto raises at
add/merge time; lode raises nothing, so the check runs here. See
the “Divergences from Ecto” guide (multi-breadth).
pub fn update(
multi m: Multi,
name name: String,
schema s: schema.Schema(row),
changeset cs: changeset.Changeset(row),
) -> Multi
Update a changeset under name.
pub fn update_all(
multi m: Multi,
name name: String,
query q: query.Query,
set sets: List(#(String, value.Value)),
) -> Multi
Bulk-update rows matching query with literal column values under name
(Ecto’s Multi.update_all, via repo.update_all). The stored result is
the affected count (Int). For expression-valued sets, use a run step
with repo.update_all_set.