Persisting values from relationships at creation time

Hi there! I'm working on a demo that allows products to be purchased by users. Since prices might change over time, I'd like to put the price of the product at the time of purchase into the order resource record. Given a product with the following attributes
attributes do
uuid_primary_key :id

attribute :name, :string do
allow_nil? false
public? true
end

attribute :price, :money do
allow_nil? false
public? true
end

create_timestamp :inserted_at
update_timestamp :updated_at
end
attributes do
uuid_primary_key :id

attribute :name, :string do
allow_nil? false
public? true
end

attribute :price, :money do
allow_nil? false
public? true
end

create_timestamp :inserted_at
update_timestamp :updated_at
end
and orders defined with
attributes do
uuid_primary_key :id

attribute :amount, :money do
allow_nil? false
public? true
end

create_timestamp :inserted_at
update_timestamp :updated_at
end

relationships do
belongs_to :user, AppName.Accounts.User do
allow_nil? false
public? true
end

belongs_to :product, AppName.DomainName.Product do
allow_nil? false
public? true
end
end
attributes do
uuid_primary_key :id

attribute :amount, :money do
allow_nil? false
public? true
end

create_timestamp :inserted_at
update_timestamp :updated_at
end

relationships do
belongs_to :user, AppName.Accounts.User do
allow_nil? false
public? true
end

belongs_to :product, AppName.DomainName.Product do
allow_nil? false
public? true
end
end
what is the best way to preserve the price value from the product? My first try adds a purchase action to the Order resource:
create :purchase_product do
accept [:product_id]
change relate_actor(:user)
change fn changeset, context ->
product_id = Ash.Changeset.get_attribute(changeset, :product_id)
product = AppName.DomainName.get_product!(product_id)
Ash.Changeset.change_attribute(changeset, :amount, product.price)
end
end
create :purchase_product do
accept [:product_id]
change relate_actor(:user)
change fn changeset, context ->
product_id = Ash.Changeset.get_attribute(changeset, :product_id)
product = AppName.DomainName.get_product!(product_id)
Ash.Changeset.change_attribute(changeset, :amount, product.price)
end
end
but this causes me to lose the builtin "not found" error response if the product_id is invalid. I also considered an after_action, but it didn't feel right to force change an attribute:
change set_attribute(:amount, "$0.00")

change after_action(fn changeset, context ->
product_id = Ash.Changeset.get_attribute(changeset, :product_id)
product = AppName.DomainName.get_product!(product_id, actor: context[:actor])
Ash.Changeset.force_change_attribute(changeset, :amount, product.price)
end)
change set_attribute(:amount, "$0.00")

change after_action(fn changeset, context ->
product_id = Ash.Changeset.get_attribute(changeset, :product_id)
product = AppName.DomainName.get_product!(product_id, actor: context[:actor])
Ash.Changeset.force_change_attribute(changeset, :amount, product.price)
end)
Solution:
``` change before_action(fn changeset, context -> product_id = Ash.Changeset.get_attribute(changeset, :product_id) product = AppName.DomainName.get_product!(product_id, actor: context.actor) ...
Jump to solution
6 Replies
RootCA
RootCAOP3mo ago
oof, I meant before_action—not after_action
Solution
RootCA
RootCA3mo ago
change before_action(fn changeset, context ->
product_id = Ash.Changeset.get_attribute(changeset, :product_id)
product = AppName.DomainName.get_product!(product_id, actor: context.actor)

Ash.Changeset.force_change_attribute(changeset, :amount, product.price)
end)
change before_action(fn changeset, context ->
product_id = Ash.Changeset.get_attribute(changeset, :product_id)
product = AppName.DomainName.get_product!(product_id, actor: context.actor)

Ash.Changeset.force_change_attribute(changeset, :amount, product.price)
end)
ZachDaniel
ZachDaniel3mo ago
That is likely how I'd do it
RootCA
RootCAOP3mo ago
Got it! I just wasn't sure if there was something obvious I was missing since it seems like a scenario that isn't the most uncommon
ZachDaniel
ZachDaniel3mo ago
Yeah, ideally in the future we will have expression basd attribute setting on creates, which only works on updates i.e change atomic_update(:amount, expr(product.price)) At some point in the future we'll have change atomc_set(:amount, expr(...))
RootCA
RootCAOP3mo ago
That sounds awesome! With that not present, I'll proceed with the before_action forced change. Thanks!

Did you find this page helpful?