Skip to content

Commit f16aa8f

Browse files
authored
Remove Ecto.Multi usage in data modelling guides (#6529)
1 parent 8b80f26 commit f16aa8f

File tree

2 files changed

+29
-25
lines changed

2 files changed

+29
-25
lines changed

guides/data_modelling/cross_context_boundaries.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -549,26 +549,26 @@ Head back over to your shopping cart context in `lib/hello/shopping_cart.ex` and
549549
cart
550550
|> Cart.changeset(attrs, scope)
551551
|> Ecto.Changeset.cast_assoc(:items, with: &CartItem.changeset/2)
552-
553-
Ecto.Multi.new()
554-
|> Ecto.Multi.update(:cart, changeset)
555-
|> Ecto.Multi.delete_all(:discarded_items, fn %{cart: cart} ->
556-
from(i in CartItem, where: i.cart_id == ^cart.id and i.quantity == 0)
552+
553+
Repo.transact(fn ->
554+
with {:ok, cart} <- Repo.update(changeset),
555+
{_count, _cart_items} = Repo.delete_all(from(i in CartItem, where: i.cart_id == ^cart.id and i.quantity == 0)) do
556+
{:ok, cart}
557+
end
557558
end)
558-
|> Repo.transact()
559559
|> case do
560-
{:ok, %{cart: cart}} ->
560+
{:ok, cart} ->
561561
broadcast_cart(scope, {:updated, cart})
562562
{:ok, cart}
563563

564-
{:error, :cart, changeset, _changes_so_far} ->
565-
{:error, changeset}
564+
{:error, reason} ->
565+
{:error, reason}
566566
end
567567
end
568568
```
569569

570-
We started much like how our out-of-the-box code started – we take the cart struct and cast the user input to a cart changeset, except this time we use `Ecto.Changeset.cast_assoc/3` to cast the nested item data into `CartItem` changesets. Remember the [`<.inputs_for />`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#inputs_for/1) call in our cart form template? That hidden ID data is what allows Ecto's `cast_assoc` to map item data back to existing item associations in the cart. Next we use `Ecto.Multi.new/0`, which you may not have seen before. Ecto's `Multi` is a feature that allows lazily defining a chain of named operations to eventually execute inside a database transaction. Each operation in the multi chain receives the values from the previous steps and executes until a failed step is encountered. When an operation fails, the transaction is rolled back and an error is returned, otherwise the transaction is committed.
570+
We started much like how our out-of-the-box code started – we take the cart struct and cast the user input to a cart changeset, except this time we use `Ecto.Changeset.cast_assoc/3` to cast the nested item data into `CartItem` changesets. Remember the [`<.inputs_for />`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#inputs_for/1) call in our cart form template? That hidden ID data is what allows Ecto's `cast_assoc` to map item data back to existing item associations in the cart.
571571

572-
For our multi operations, we start by issuing an update of our cart, which we named `:cart`. After the cart update is issued, we perform a multi `delete_all` operation, which takes the updated cart and applies our zero-quantity logic. We prune any items in the cart with zero quantity by returning an ecto query that finds all cart items for this cart with an empty quantity. Calling `Repo.transact/1` with our multi will execute the operations in a new transaction and we return the success or failure result to the caller just like the original function.
572+
Once the `changeset` is ready, we wrap everything in `Repo.transact/2` so the operations run safely as one. Inside the transaction, we update the cart using `Repo.update/1`. If the update succeeds, we follow up with a cleanup step using `Repo.delete_all/2` to remove any cart items with zero quantity. Running both steps in the same transaction prevents partial updates and keeps the cart data accurate. Finally, we broadcast the updated cart so that any connected LiveViews can instantly show the changes.
573573

574574
Let's head back to the browser and try it out. Add a few products to your cart, update the quantities, and watch the values changes along with the price calculations. Setting any quantity to 0 will also remove the item. You can also try logging out and registering a new user to see how the carts are scoped to the current user. Pretty neat!

guides/data_modelling/more_examples.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -207,34 +207,38 @@ From our requirements alone, we can start to see why a generic `create_order` fu
207207

208208
line_items =
209209
Enum.map(cart.items, fn item ->
210-
%{product_id: item.product_id, price: item.product.price, quantity: item.quantity}
210+
%{
211+
product_id: item.product_id,
212+
price: item.product.price,
213+
quantity: item.quantity
214+
}
211215
end)
212216

213-
order =
214-
Ecto.Changeset.change(%Order{},
217+
order_changeset =
218+
Ecto.Changeset.change(%Order{}, %{
215219
user_id: scope.user.id,
216220
total_price: ShoppingCart.total_cart_price(cart),
217221
line_items: line_items
218-
)
222+
})
219223

220-
Ecto.Multi.new()
221-
|> Ecto.Multi.insert(:order, order)
222-
|> Ecto.Multi.run(:prune_cart, fn _repo, _changes ->
223-
ShoppingCart.prune_cart_items(scope, cart)
224+
Repo.transact(fn ->
225+
with {:ok, order} <- Repo.insert(order_changeset),
226+
{:ok, _cart} <- ShoppingCart.prune_cart_items(scope, cart) do
227+
{:ok, order}
228+
end
224229
end)
225-
|> Repo.transact()
226230
|> case do
227-
{:ok, %{order: order}} ->
231+
{:ok, order} ->
228232
broadcast_order(scope, {:created, order})
229233
{:ok, order}
230-
231-
{:error, name, value, _changes_so_far} ->
232-
{:error, {name, value}}
234+
235+
{:error, reason} ->
236+
{:error, reason}
233237
end
234238
end
235239
```
236240

237-
We started by mapping the `%ShoppingCart.CartItem{}`'s in our shopping cart into a map of order line items structs. The job of the order line item record is to capture the price of the product *at payment transaction time*, so we reference the product's price here. Next, we create a bare order changeset with `Ecto.Changeset.change/2` and associate our user UUID, set our total price calculation, and place our order line items in the changeset. With a fresh order changeset ready to be inserted, we can again make use of `Ecto.Multi` to execute our operations in a database transaction. We start by inserting the order, followed by a `run` operation. The `Ecto.Multi.run/3` function allows us to run any code in the function which must either succeed with `{:ok, result}` or error, which halts and rolls back the transaction. Here, we simply call into our shopping cart context and ask it to prune all items in a cart. Running the transaction will execute the multi as before and we return the result to the caller.
241+
We started by mapping the `%ShoppingCart.CartItem{}`'s in our shopping cart into a map of order line items structs. The job of the order line item record is to capture the price of the product *at payment transaction time*, so we reference the product's price here. Next, we create a bare order changeset with `Ecto.Changeset.change/2` and associate our user UUID, set our total price calculation, and place our order line items in the changeset. With a fresh order changeset ready to be inserted, we now make use of `Repo.transact/2` to execute our operations in a database transaction. We start by inserting the order, followed by a step that prunes all items from the user’s cart. The function wrapped inside `Repo.transact/2` must either return `{:ok, result}` or error, which halts and rolls back the transaction. Running the transaction will execute these operations in sequence, and we return the result to the caller once completed.
238242

239243
To close out our order completion, we need to implement the `ShoppingCart.prune_cart_items/1` function in `lib/hello/shopping_cart.ex`:
240244

0 commit comments

Comments
 (0)