How to insert a record with its nested children?

First, I'd love to thank everyone and especially Zash for the awesome work done in Ash I have a few resources with some relationships between them, for brevity I'll only copy the relevant parts
defmodule Example.Orders.Order do
use Ash.Resource

attributes do
uuid_primary_key :id
attribute :state, :atom, allow_nil?: false
end

relationships do
has_many :lines, Example.Orders.OrderLine
end
end
defmodule Example.Orders.Order do
use Ash.Resource

attributes do
uuid_primary_key :id
attribute :state, :atom, allow_nil?: false
end

relationships do
has_many :lines, Example.Orders.OrderLine
end
end
defmodule Example.Orders.OrderLine do
use Ash.Resource

attributes do
uuid_primary_key :id
attribute :quantity, :decimal, allow_nil?: false
end

relationships do
belongs_to :order, Example.Orders.Order

belongs_to :product, Example.Products.Product do
api Example.Products
allow_nil? false
attribute_writable? true
end
end
end
defmodule Example.Orders.OrderLine do
use Ash.Resource

attributes do
uuid_primary_key :id
attribute :quantity, :decimal, allow_nil?: false
end

relationships do
belongs_to :order, Example.Orders.Order

belongs_to :product, Example.Products.Product do
api Example.Products
allow_nil? false
attribute_writable? true
end
end
end
What I wish to do is to insert an order along with its lines like this
Example.Orders.Order.create!(%{
state: :draft,
lines: [%{
product_id: ...,
quantity: ...
}]
})
Example.Orders.Order.create!(%{
state: :draft,
lines: [%{
product_id: ...,
quantity: ...
}]
})
I could make it using manage_relationship, but it seems to require lines to be sent as an argument to the create action and not as a part of the order itself. I also tried Ash.Resource.Change like this
defmodule Example.Orders.Order.Changes.SetLines do
use Ash.Resource.Change

def change(changeset, _, _) do
lines = Ash.Changeset.get_attribute(changeset, :lines)

changeset
|> Ash.Changeset.manage_relationship(
:lines,
lines,
type: :direct_control
)
end
end
defmodule Example.Orders.Order.Changes.SetLines do
use Ash.Resource.Change

def change(changeset, _, _) do
lines = Ash.Changeset.get_attribute(changeset, :lines)

changeset
|> Ash.Changeset.manage_relationship(
:lines,
lines,
type: :direct_control
)
end
end
elixir And updated the create action in order resource to be like this
create :create do
change Example.Orders.Order.Changes.SetLines
end
create :create do
change Example.Orders.Order.Changes.SetLines
end
but it doesn't work, and it seems that :lines always get removed from changeset before the change code runs. Am I missing something? I don't mind sending lines as an argument to the action in this case, but then what happens with multiple levels of nesting?
2 Replies
ZachDaniel
ZachDaniel•2y ago
👋 So managed relationships is the way to accomplish this. Check out the docs for Ash.Changeset.manage_relationship (there is a lot there, sorry 😆 ). when managing relationships, we call actions on the destination resources based on the manage relationsh options. This defaults to the primary actions of a given type on a resource. Here is what you'd do
efmodule Example.Orders.Order do
use Ash.Resource

actions do
create :create do
argument :lines, {:array, :map}
change manage_relationship(:lines, type: :direct_control)
end
end

...
end
efmodule Example.Orders.Order do
use Ash.Resource

actions do
create :create do
argument :lines, {:array, :map}
change manage_relationship(:lines, type: :direct_control)
end
end

...
end
That would let you do create(... %{attributes, ...., lines: [%{}, ...]}) and it will call teh relevant actions on OrderLine
Emad Shaaban
Emad ShaabanOP•2y ago
Thanks so much for the instant reply Zash, You keep amazing me I just tried it and it works well, I think I was almost there .. only needed to add the argument line in the create action. I feel dumb I didn't try that before asking 😦 I think what confused me is that I didn't expect manage_relationship to pick arguments from inside the main record :thinkies:, I used arguments for other actions and always sent them as extra parameters, maybe that's why.

Did you find this page helpful?