I want to calculate the sub_total for a sale (sum of line_items.line_total) without querying the data layer. how can I achieve that?
Here’s my current setup for the sub_total calculation in the Sale resource:
calculations do
calculate :sub_total, :decimal, expr(sum(line_items.line_total)) do
description "Sub total amount for the sale before discounts or taxes"
public? true
end
end`
I’d like to calculate the sub_total using Ash.calculate!/3 by passing line_items data in-memory, like this:
For context, I’m already doing something similar for line_total in the SaleLineItem resource, which works without hitting the data layer:
calculations do
calculate :line_total, :decimal, expr(quantity * price) do
public? true
description "Total amount for the line item"
end
end`
# I can compute line_total like this:
line_item_attrs = %{quantity: 2, price: 10}
Ash.calculate!(SaleLineItem, :line_total, refs: line_item_attrs)`
How can I configure the sub_total calculation to work with in-memory data (sale_attrs) and avoid hitting the data layer, similar to my line_total calculation?
calculate :sub_total, :decimal, expr(sum(line_items, field: :line_total)) do
description "Sub total amount for the sale before discounts or taxes"
public? true
end
Thanks @zachdaniel , but it is still failing. It seems like it will still have to hit the table. I wanted to store the logic to compute sub_total in a calculation without having to hit the database, especially for uncreated orders, but it seems like I will have to hit the database.
I extracted it into a calculation module and realised that this is being treated as a query to a relationship, thus the failure to calculate without querying the database or setting the tenant especially since my app is a multitenant app. It does not make sense why the line item calculation works, but not the sub_total calculation on sale.
defmodule MyApp.Sales.Sale.Calculations.SubTotal do
use Ash.Resource.Calculation
@impl true
def calculate(sale, opts, arguments) do
{:ok, Decimal.new(0)}
end
end
Calculation definition
calculations do
calculate :sub_total, :decimal, MyApp.Sales.Sale.Calculations.SubTotal do
description "Sub total amount for the sale before any discounts or taxes"
public? true
end
end
It was simpler than I thought! I followed the contract of extracting a calculation in its module and it worked.
# MyApp.Sales.Sale resource
calculations do
calculate :sub_total, :decimal, MyApp.Sales.Sale.Calculations.SubTotal
end
Calculation Module
defmodule MyApp.Sales.Sale.Calculations.SubTotal do
use Ash.Resource.Calculation
def description, do: "Calculates the sub total of a sale."
def public?, do: true
@impl true
def load(_query, opts, _context) do
[line_items: [:line_total, :quantity, :price]]
end
@impl true
def calculate(sales, _opts, _arguments) do
Enum.map(sales, &calculate_sub_total/1)
end
defp calculate_sub_total(%{line_items: line_items}) do
line_items
|> Enum.map(& &1.line_total)
|> Enum.reduce(Decimal.new(0), &Decimal.add/2)
end
end
Now I am able to successful calculate subtotal without hitting the DB