Builder Guide

View Source

Builders provide a functional, composable way to construct Ecto.Multi transactions.

Why Use Builders?

Builders are ideal when you need:

  • Conditional logic - Different steps based on parameters
  • Dynamic composition - Build transactions programmatically
  • Maximum flexibility - Full control over the flow
  • Reusable components - Share transaction fragments

Basic Usage

import MultiFlow.Builders
alias Ecto.Multi

def create_order(params) do
  Multi.new()
  |> run_step(:validate, fn _ -> validate(params) end)
  |> insert_step(:order, Order, &build_order/1)
  |> insert_all_step(:items, OrderItem, &build_items/1)
  |> MyApp.Repo.transaction()
end

Builder Functions

run_step/3

Execute a custom function:

multi
|> run_step(:step_name, fn changes ->
  {:ok, %{result: "success"}}
end)

insert_step/4

Insert a single record:

multi
|> insert_step(:user, User, fn changes ->
  %User{name: changes.params.name}
end)

insert_all_step/4

Bulk insert records:

multi
|> insert_all_step(:items, Item, fn changes ->
  [
    %{name: "Item 1"},
    %{name: "Item 2"}
  ]
end)

update_step/3

Update a record:

multi
|> update_step(:user, fn changes ->
  Ecto.Changeset.change(changes.user, %{updated: true})
end)

update_all_step/4

Bulk update:

multi
|> update_all_step(:stock,
  fn _ -> from(s in Stock, where: s.qty < 10) end,
  fn _ -> [set: [updated_at: DateTime.utc_now()]] end
)

conditional_step/4

Conditional execution:

multi
|> conditional_step(user.is_vip?, :vip_discount, fn changes ->
  {:ok, %{discount: 0.2}}
end)

retry_step/5

Retry on failure:

multi
|> retry_step(:api_call, 3, 1000, fn changes ->
  call_external_api(changes)
end)

Advanced Patterns

Reusable Fragments

defmodule CommonSteps do
  import MultiFlow.Builders
  
  def add_customer_validation(multi, params) do
    multi
    |> run_step(:validate_customer, fn _ ->
      validate_customer(params.customer_id)
    end)
    |> run_step(:check_credit, fn _ ->
      check_customer_credit(params.customer_id)
    end)
  end
end

# Use in your transaction
Multi.new()
|> CommonSteps.add_customer_validation(params)
|> insert_step(:order, Order, &build_order/1)

Grouping Steps

multi
|> group(:order_creation, fn multi ->
  multi
  |> insert_step(:order, Order, &build_order/1)
  |> insert_all_step(:items, OrderItem, &build_items/1)
end)
|> group(:payment_processing, fn multi ->
  multi
  |> insert_step(:payment, Payment, &build_payment/1)
  |> update_step(:order, &mark_as_paid/1)
end)

Tap for Side Effects

multi
|> tap_step(:log_start, fn changes ->
  Logger.info("Transaction started")
  changes
end)
|> insert_step(:record, Record, &build/1)
|> tap_step(:log_end, fn changes ->
  Logger.info("Transaction completed")
  changes
end)

Complete Example

See examples/sales_order.ex for a production-ready example using builders.