Updating array embeds

https://ash-hq.org/docs/guides/ash/latest/topics/embedded-resources says this for Array embeds
All values in the original array are destroyed, and all maps in the new array are used to create new records.
All values in the original array are destroyed, and all maps in the new array are used to create new records.
Ecto has an api to update array embeds while keeping existing ids. Is there a way to do the same using Ash?
Ash HQ
Guide: Embedded Resources
Read the "Embedded Resources" guide on Ash HQ
9 Replies
ZachDaniel
ZachDaniel3y ago
When you add a primary key to the embed, it should behave that way: https://ash-hq.org/docs/guides/ash/latest/topics/embedded-resources#array-embeds-with-primary-keys
Ash HQ
Guide: Embedded Resources
Read the "Embedded Resources" guide on Ash HQ
ZachDaniel
ZachDaniel3y ago
that is just the behavior that it has when you don't add a primary key to the embed
Jason
JasonOP3y ago
I see. Should the code look like this? The example code seems to show only Ash.Changeset.new.
comment
|> Ash.Changeset.for_update(:update, attr)
|> Tweets.update()
comment
|> Ash.Changeset.for_update(:update, attr)
|> Tweets.update()
ZachDaniel
ZachDaniel3y ago
I should remove that sample code then, because generally speaking Ash.Changeset.new() shouldn't be used unless you explicitly need to make some modifications to the changeset before the action is called. but yes it would look roughly like that, and you'd have %{embedded_list: [%{id: id1}, %{id: id2}]} keep in mind that with the current logic it has you still need to include all items in the list.
Jason
JasonOP3y ago
Okay. Let me try. thank you!
ZachDaniel
ZachDaniel3y ago
Also keep in mind: if you supply a list of structs, the structs just become the new value, no matching by id is performed. So what you can do if you want some custom update
update :modify_embed do
argument :embed_id, :uuid do
allow_nil? false
end

argument :changes, :map do
allow_nil? false
end

change fn changeset, _ ->
original_value = changeset.data.embeds
end
end
update :modify_embed do
argument :embed_id, :uuid do
allow_nil? false
end

argument :changes, :map do
allow_nil? false
end

change fn changeset, _ ->
original_value = changeset.data.embeds
end
end
Thats just one example, but You can accept arbitrary input in actions, and use that input to make your own changes to the list of structs, and then set the new value.
Jason
JasonOP3y ago
Thanks. I tried this. There is no error but it's not being recorded to the DB.
params = %{
"embeds" => current_embed ++ [attr]
}

item
|> Ash.Changeset.for_update(:update_embeds, params)
|> IO.inspect(label: "tweets.update().changeset")
|> Tweets.update()
|> IO.inspect(label: "tweets.update()")

----------------
update :update_embeds do
argument :embeds, {:array, Embed} do
allow_nil? false
end

change fn changeset, _ ->
Ash.Changeset.force_change_attribute(
changeset,
:embeds,
Ash.Changeset.get_argument(changeset, :embeds)
)

IO.inspect(changeset, label: "update_embeds changeset")
end
end
-----------------------
params = %{
"embeds" => current_embed ++ [attr]
}

item
|> Ash.Changeset.for_update(:update_embeds, params)
|> IO.inspect(label: "tweets.update().changeset")
|> Tweets.update()
|> IO.inspect(label: "tweets.update()")

----------------
update :update_embeds do
argument :embeds, {:array, Embed} do
allow_nil? false
end

change fn changeset, _ ->
Ash.Changeset.force_change_attribute(
changeset,
:embeds,
Ash.Changeset.get_argument(changeset, :embeds)
)

IO.inspect(changeset, label: "update_embeds changeset")
end
end
-----------------------
<Console outputs>

iex(581)> tweets.update().changeset: #Ash.Changeset<
action_type: :update,
action: :update_embeds,
attributes: %{},
relationships: %{},
arguments: %{
eembeds: [
#Tweet.Tweets.Embed<
__meta__: #Ecto.Schema.Metadata<:built, "">,
id: "31f0d0a3-1e1d-4e5e-87cd-a52712788100",
inserted_at: ~U[2023-02-27 00:14:29Z],
updated_at: ~U[2023-02-27 00:14:29Z],
aggregates: %{},
calculations: %{},
__order__: nil,
...
>
]
},
errors: [],
data: #Tweet.Tweets.Item<
tweet: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded, "items">,
id: "8a40bf68-d82f-4a12-a9ae-866998c3fa45",
embeds: nil, <<<<<<<<<<<<<<<<<<<<<<<<<<
inserted_at: ~U[2023-02-18 20:16:49Z],
updated_at: ~U[2023-02-18 20:16:49Z],
aggregates: %{},
calculations: %{},
__order__: nil,
...
>,
valid?: true
>
iex(581)> [debug] QUERY OK db=0.4ms idle=1242.7ms
begin []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1533
iex(581)> [debug] QUERY OK db=0.2ms
commit []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1533
iex(581)> tweets.update(): #Tweet.Tweets.Item<
tweet: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded, "items">,
id: "8a40bf68-d82f-4a12-a9ae-866998c3fa45",
embeds: nil,
inserted_at: ~U[2023-02-18 20:16:49Z],
updated_at: ~U[2023-02-18 20:16:49Z],
aggregates: %{},
calculations: %{},
__order__: nil,
<Console outputs>

iex(581)> tweets.update().changeset: #Ash.Changeset<
action_type: :update,
action: :update_embeds,
attributes: %{},
relationships: %{},
arguments: %{
eembeds: [
#Tweet.Tweets.Embed<
__meta__: #Ecto.Schema.Metadata<:built, "">,
id: "31f0d0a3-1e1d-4e5e-87cd-a52712788100",
inserted_at: ~U[2023-02-27 00:14:29Z],
updated_at: ~U[2023-02-27 00:14:29Z],
aggregates: %{},
calculations: %{},
__order__: nil,
...
>
]
},
errors: [],
data: #Tweet.Tweets.Item<
tweet: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded, "items">,
id: "8a40bf68-d82f-4a12-a9ae-866998c3fa45",
embeds: nil, <<<<<<<<<<<<<<<<<<<<<<<<<<
inserted_at: ~U[2023-02-18 20:16:49Z],
updated_at: ~U[2023-02-18 20:16:49Z],
aggregates: %{},
calculations: %{},
__order__: nil,
...
>,
valid?: true
>
iex(581)> [debug] QUERY OK db=0.4ms idle=1242.7ms
begin []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1533
iex(581)> [debug] QUERY OK db=0.2ms
commit []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1533
iex(581)> tweets.update(): #Tweet.Tweets.Item<
tweet: #Ash.NotLoaded<:relationship>,
__meta__: #Ecto.Schema.Metadata<:loaded, "items">,
id: "8a40bf68-d82f-4a12-a9ae-866998c3fa45",
embeds: nil,
inserted_at: ~U[2023-02-18 20:16:49Z],
updated_at: ~U[2023-02-18 20:16:49Z],
aggregates: %{},
calculations: %{},
__order__: nil,
ZachDaniel
ZachDaniel3y ago
you're not returning the changeset that you're changing the attribute on the IO.inspect(changeset) is causing it to return the original changeset
Jason
JasonOP3y ago
Oops... thanks 🙂

Did you find this page helpful?