Nested aggregates

In my Product Data Management webapp I'm trying to build with Ash, I have the following resources with attributes and relatioships in App.Api that, combined, construct/define an Item. * Category: has a :code attribute * Family: belongs_to a Category (and also has a :name, etc.) * Variant: belongs_to a Variant, has a :code attribute The idea is that an Item belongs_to a Category and a Variant, and has a calculated attribute (also called :code) resulting from concatenating the Category :code and the Variant :code. This means that Item's :code calculation must "pull" Category's :code through the Variant it belongs to, which belongs to a Family, which belongs to a Category. Item's calculated :code uniquely identifies a record. I have tried the following in a cascade, In lib/app/resources/item.ex:
aggregates do
first :category_code, :variant, :category_code
end
aggregates do
first :category_code, :variant, :category_code
end
In lib/app/resources/variant.ex:
aggregates do
first :category_code, :family, :category_code
end
aggregates do
first :category_code, :family, :category_code
end
In lib/app/resources/family.ex:
aggregates do
first :category_code, :category, :code
end
aggregates do
first :category_code, :category, :code
end
...hoping that App.Api.Item |> App.Api.read! would return records on which I can App.Api.load!(:code). Instead:
** (CaseClauseError) no case clause matching: {:error, [%Ash.Error.Unknown.UnknownError{error: "Must provide field type for first", field: nil, changeset: nil, query: nil, error_context: ["Loading aggregate: :category_code for query: #Ash.Query<resource: App.Api.Variant>"], vars: [], path: [:aggregates], stacktrace: #Stacktrace<>, class: :unknown}]}
** (CaseClauseError) no case clause matching: {:error, [%Ash.Error.Unknown.UnknownError{error: "Must provide field type for first", field: nil, changeset: nil, query: nil, error_context: ["Loading aggregate: :category_code for query: #Ash.Query<resource: App.Api.Variant>"], vars: [], path: [:aggregates], stacktrace: #Stacktrace<>, class: :unknown}]}
What is the Ash-idiomatic way of achieving the goal of being able to access Item's :code?
24 Replies
ZachDaniel
ZachDaniel3y ago
You can’t currently reference aggregates from other aggregates However aggregates accept a relationship path. So you can say first [:foo, :bar, :baz], :code I need to give that a better error for sure.
waseigo
waseigoOP3y ago
first [:variant, :family, :category], :code ? it's getting marked as an error in VSCode (first/2 is not defined
ZachDaniel
ZachDaniel3y ago
Ah, sorry My example was bad The first argument is the name So it’s name, path, field
waseigo
waseigoOP3y ago
Awesome! I also just realized that eval automatically casts the arguments to strings?
aggregates do
first :variant_code, :variant, :code
first :category_code, [:variant, :family, :category], :code
end

calculations do
calculate :code, :string, expr(category_code <> variant_code)
end
aggregates do
first :variant_code, :variant, :code
first :category_code, [:variant, :family, :category], :code
end

calculations do
calculate :code, :string, expr(category_code <> variant_code)
end
Both aggregates refer to :integer types in the other resources, but the result of the :code calculation is a :string
ZachDaniel
ZachDaniel3y ago
Yeah, because the :<> operator is typed 🙂
waseigo
waseigoOP3y ago
nice! Thank you, wow are there lots of things I'm learning, thank you for all the help!
ZachDaniel
ZachDaniel3y ago
Glad to be of assistance!
waseigo
waseigoOP3y ago
Though, now this works:
preparations do
prepare build(load: [:series_prefix, :category_code, :variant_code])
end
preparations do
prepare build(load: [:series_prefix, :category_code, :variant_code])
end
but this doesn't:
preparations do
prepare build(load: [:series_prefix, :category_code, :variant_code, :code])
end
preparations do
prepare build(load: [:series_prefix, :category_code, :variant_code, :code])
end
I'm getting ** (Postgrex.Error) ERROR 42883 (undefined_function) operator does not exist: bigint || bigint.
ZachDaniel
ZachDaniel3y ago
🥲 I think you may need the type casts then
waseigo
waseigoOP3y ago
(I learned about preparations from another #support post)
ZachDaniel
ZachDaniel3y ago
That is likely a bug in ash_postgres
waseigo
waseigoOP3y ago
as a workaround? how?
ZachDaniel
ZachDaniel3y ago
Wrapping the things you’re concatenating with type(thing, :string)
waseigo
waseigoOP3y ago
wait, in the calculations or in the preparations block?
ZachDaniel
ZachDaniel3y ago
In the calculation Like the typed operator thing I mentioned must not be doing the right thing for ash_postgres
waseigo
waseigoOP3y ago
calculate :code, :string, expr(type(series_prefix, :string) <> type(category_code, :integer) <> type(variant_code, :integer))
calculate :code, :string, expr(type(series_prefix, :string) <> type(category_code, :integer) <> type(variant_code, :integer))
same error
ZachDaniel
ZachDaniel3y ago
The codes have to be cast to strings also
waseigo
waseigoOP3y ago
let me check, can I :load it or is it a preparations issue?
ZachDaniel
ZachDaniel3y ago
Ash doesn’t always go to the database to load things
waseigo
waseigoOP3y ago
in the aggregates first definitions?
ZachDaniel
ZachDaniel3y ago
So loading it after the fact might work In the example you showed change integer to string
waseigo
waseigoOP3y ago
yep, it works now! also, loading :code after the fact works when I didn't add it to the prepare statement this is one of the most important things I learned
ZachDaniel
ZachDaniel3y ago
Yeah because Ash will sometimes choose to figure out calculations without going to the database. And so the elixir evaluation of that must work But the postgres one doesn’t Can you log a bug in ash_postgres?
waseigo
waseigoOP3y ago
GitHub
Preparation does not work on a calculation, except when its paramet...
Describe the bug Preparation does not work on a calculation, except when its parameters are cast as strings. To Reproduce The following is a part of resource App.Api.Item. A Variant has_one Item, a...

Did you find this page helpful?