AE
Ash Elixirβ€’3y ago
waseigo

Can a calculation use attributes of a related resource?

E.g., I have belongs_to :brand, App.Portfolio.Brand and calculate :tagline, :string, expr(brand.name <> " β€” " <> name) The result:
App.Portfolio.Family
|> Ash.Query.load(:brand)
|> Ash.Query.load(:tagline)
|> App.Portfolio.read!()
** (RuntimeError) Error while building reference: brand.name
App.Portfolio.Family
|> Ash.Query.load(:brand)
|> Ash.Query.load(:tagline)
|> App.Portfolio.read!()
** (RuntimeError) Error while building reference: brand.name
18 Replies
ZachDaniel
ZachDanielβ€’3y ago
It cannot πŸ™‚ To do that, you'd define a first type aggregate, and refer to that
aggregates do
first :brand_name, :brand, :name
end

calculate :tagline, :string, expr(brand_name <> " - " <> name)
aggregates do
first :brand_name, :brand, :name
end

calculate :tagline, :string, expr(brand_name <> " - " <> name)
waseigo
waseigoOPβ€’3y ago
Thank you, clear! Then, in iex, I do App.Portfolio.load(f, :tagline), where f is a record of type App.Portfolio.Family. (For reference, for others.) Thanks, @Zach Daniel!
ZachDaniel
ZachDanielβ€’3y ago
my pleasure
waseigo
waseigoOPβ€’3y ago
And is it possible to set an identity on an aggregate? (Probably not, huh?)
ZachDaniel
ZachDanielβ€’3y ago
Correct, that is not possible Well...it might be? but it won't create a unique index when making migrations, for example What would you want the identity for?
waseigo
waseigoOPβ€’3y ago
I'm rewriting a Product Data Management thingy I built last year in Python/FastAPI, now with Ash, and instead of copying text between tables, I build my database schema properly. So, a sales :item has a unique 7-digit code. The first 3 digits come from the :code field of the product :category it belongs to. The last 4 digits come from the :code field of the product :variant it is linked with. Within App.Portfolio.Item I define two aggregates (:category_code, :variant_code) as you explained above. Then I was thinking of defining a calculation that concatenates the two aggregates into an :item_code calculated field. The idea is to be able to lookup an App.Portfolio.Item by this :item_code. Possible?
ZachDaniel
ZachDanielβ€’3y ago
Yes, definitely possible πŸ™‚ I think the identity may work for you in that case, actually But you also don't necessarily need it for look ups
waseigo
waseigoOPβ€’3y ago
Ah, so it works on the calculation
ZachDaniel
ZachDanielβ€’3y ago
You can do:
Resource
|> Ash.Query.filter(item_code == <whatever>)
|> Api.read_one()
Resource
|> Ash.Query.filter(item_code == <whatever>)
|> Api.read_one()
and things like that. There are various ways to do "get" style actions with or without a corresponding identity
waseigo
waseigoOPβ€’3y ago
I also noticed that Ash.Resource.Relationships.BelongsTo has both attribute_writable? and writable? whereas Ash.Resource.Relationships.HasOne only has writable?
ZachDaniel
ZachDanielβ€’3y ago
Yeah, so belongs to relationships add an attribute, i.e belongs_to :user adds user_id You can control the writability of both the relationship and the generated attribute.
waseigo
waseigoOPβ€’3y ago
Yep, got it -- but HasOne doesn't have the attribute_writable? option
ZachDaniel
ZachDanielβ€’3y ago
Correct, because for has_one, the attribute that changes is on the other resource.
waseigo
waseigoOPβ€’3y ago
Aha... I see what you mean. To associate A with B, if A has_one B, can you set the relationship field during :create / with an :update, or do you write A's :id into B's a_id ?
ZachDaniel
ZachDanielβ€’3y ago
You’d write A’s id into B. has_one is actually not very common in the wild. It’s exactly the same as has_many except the destination is unique on the destination attribute.
waseigo
waseigoOPβ€’3y ago
Great, it works I was trying with :update by passing the record directly and it didn't work @Zach Daniel , actually, I see that I cannot pass the record directly to the relationship attribute. After load, I get nil on the attribute value. Is my understanding correct that :create and :update can only set attributes that are not relationships?
ZachDaniel
ZachDanielβ€’3y ago
By default they accept all public, writable attributes.
waseigo
waseigoOPβ€’3y ago
I think I lean too much on my Django ORM experience, and this confuses me πŸ˜†

Did you find this page helpful?