How to compose relationship custom calculations with parent aggregates?

I have two resources Entity and Transactions.

An entity has many transactions:

    has_many :transactions, Transaction

Now, I want to calculate an aggregate in an entity using a custom calculation from transactions.

This is what I’m trying right now:

defmodule MyCalculation do
  use Ash.Resource.Calculation

  def expression(_opts, _context) do
    expr(fragment("coalesce(?, ?)", held_for, now() - buy_date))
  end
end

calculation_query = 
  Transaction
  |> Ash.Query.calculate(:held_for_calc, :integer, MyCalculation)

Entity
|> Ash.Query.filter(id == "0190e71e-7b79-7be7-a1f6-2e34d3635670")
|> Ash.Query.load(transactions: calculation_query)
|> Ash.Query.aggregate(:average_years_held, :avg, :transactions, field: :held_for_calc)
|> Ash.read_one!()

So, the idea is that I load the transactions with my calculation_query and then use that value to do the aggregate.

The issue with this is that the aggregate will not find the field since it is not an attribute or calculation defined in the Transaction resource giving me the following error:

** (RuntimeError) No such field for Core.Pacman.Markets.Transaction: :held_for_calc
    (ash 3.5.23) lib/ash/query/aggregate.ex:314: Ash.Query.Aggregate.new/4
    (ash 3.5.23) lib/ash/query/query.ex:3182: Ash.Query.aggregate/5
    iex:27: (file)

How can I make the aggregate see the custom calculation value?

The field for an aggregate can be a calculation created with Ash.Query.Calculation.new which is how you’d accomplish this particular goal.

I’m not sure I understand what you meant here. Can you elaborate a little bit more on how I would use Ash.Query.Calculation.new in my query to make it work?

I tried using Ash.Query.Calculation.new but I still got the same error, I guess this is not the way you meant to use it:

{:ok, calculation} = Ash.Query.Calculation.new(:held_for_calc, SomeCalculation, [], :integer, [])

query = Transaction |> Ash.Query.load(calculation)

Entity
|> Ash.Query.load(transactions: query)
|> Ash.Query.filter(id == "0190e71e-7b79-7be7-a1f6-2e34d3635670")
|> Ash.Query.aggregate(:average_years_held, :avg, :transactions, field: :held_for_calc)
|> Ash.read_one!()

Sorry, was on my phone and thought I’d throw a Hail Mary in case it helped :laughing: Back at my laptop:

{:ok, calculation} = Ash.Query.Calculation.new(:held_for_calc, SomeCalculation, [], :integer, [])

Entity
|> Ash.Query.load(transactions: query)
|> Ash.Query.filter(id == "0190e71e-7b79-7be7-a1f6-2e34d3635670")
|> Ash.Query.aggregate(:average_years_held, :avg, :transactions, field: calculation)
|> Ash.read_one!()
1 Like

Amazing, that worked like a charm!! Thanks!

1 Like