Using manage_relationship to delete a related record
moxley7725:
I have an action that accepts the ID of a related record. It should delete that record. However, when I tried using the
type: :remove
option, Ash responds with
Invalid value provided for notes: changes would create a new related record.
Is this the right approach? Or should I manually delete the record?
moxley7725:
Here’s the action definition:
update :delete_note do
argument :id, :string
change(&Member2Util.delete_note/2)
end
Here’s the referenced
delete_note()
function:
def delete_note(changeset, _context) do
note_id = changeset.arguments[:id]
note_attrs = %{id: note_id}
Changeset.manage_relationship(changeset, :notes, [note_attrs],
type: :remove,
on_lookup: :ignore
)
end
zachdaniel:
Something does seem strange there…
zachdaniel:
I don’t think
type: :remove
will do the thing as that will attempt to “unrelate” the two things
zachdaniel:
You might try
manage_relationship(...., on_match: {:destroy, :destroy_action_name})
moxley7725:
This works:
note_attrs = %{id: to_string(orig_note.id)}
_updated_member2 =
member2
|> Ash.Changeset.for_update(:update, %{}, authorize?: false)
|> Ash.Changeset.manage_relationship(:notes, [note_attrs], on_match: {:destroy, :destroy})
|> GF.Ash.update!()
It successfully deletes the related record.
This is a
Member
has_many
Notes
relationship.
moxley7725:
I set
on_match: {:destroy, :destroy}
.
moxley7725:
I also did it to the original code:
def delete_note(changeset, _context) do
note_id = changeset.arguments[:id]
note_attrs = %{id: note_id}
Changeset.manage_relationship(changeset, :notes, [note_attrs], on_match: {:destroy, :destroy})
|> dbg()
end
moxley7725:
But it still doesn’t delete the related record.
moxley7725:
I ran an
IO.inspect
at the end of the function above, and it looks exactly like the
IO.inspect
I applied when calling
manage_relationship
directly.
moxley7725:
They both contain this piece of data:
relationships: %{
notes: [
{[%{id: "446"}],
[
ignore?: false,
on_missing: :ignore,
on_lookup: :ignore,
on_no_match: :ignore,
eager_validate_with: false,
authorize?: true,
on_match: {:destroy, :destroy},
meta: [inputs_was_list?: true]
]}
]
}
zachdaniel:
Hard to follow the specifics. If you could reproduce the behavior in a test that would help a lot!
moxley7725:
Here’s the test:
note_attrs = %{id: to_string(orig_note.id)}
_updated_member2 =
member2
|> Ash.Changeset.for_update(:delete_note, note_attrs, actor: ctx.session_member)
|> GF.Ash.update!()
member =
GF.Members.Member2
|> GF.Ash.get!(member2.id, authorize?: false)
|> GF.Ash.load!(:notes)
# Fails. Note is not deleted.
assert member.notes == []
moxley7725:
Here’s the action:
update :delete_note do
argument :id, :string
change(fn changeset, _context ->
note_attrs = changeset.arguments
Changeset.manage_relationship(changeset, :notes, [note_attrs],
on_match: {:destroy, :destroy}
)
end)
end
moxley7725:
Calling
manage_relationship
doesn’t delete the note either:
note_attrs = %{id: to_string(orig_note.id)}
_updated_member2 =
member2
|> Ash.Changeset.for_update(:update, %{}, authorize?: false,
actor: ctx.session_member)
|> Ash.Changeset.manage_relationship(:notes, [note_attrs], on_match: {:destroy, :destroy})
|> GF.Ash.update!()
member =
GF.Members.Member2
|> GF.Ash.get!(member2.id, authorize?: false)
|> GF.Ash.load!(:notes)
# Fails. Note is not deleted.
assert member.notes == []
moxley7725:
Okay, I think this has something to do with with converting between string and integer IDs
moxley7725:
In the last test I posted, if I change this line,
note_attrs = %{id: to_string(orig_note.id)}
to
note_attrs = %{id: orig_note.id}
,
it passes.
moxley7725:
That was it.
moxley7725:
I modified my action to this:
update :delete_note do
argument :id, :string
change(fn changeset, _context ->
note_attrs = %{id: String.to_integer(changeset.arguments[:id])}
Changeset.manage_relationship(changeset, :notes, [note_attrs],
on_match: {:destroy, :destroy}
)
end)
end
I cast the string
:id
argument to an integer before calling
manage_relationship
.
moxley7725:
However, there seems to be the opposite issue when calling
manage_relationship
with
type: :append
:
# note_attrs = %{id: to_string(orig_note.id), body: "updated note"}
note_attrs = %{id: orig_note.id, body: "updated note"}
member
|> Ash.Changeset.for_update(:update, %{}, actor: session_member)
|> Ash.Changeset.manage_relationship(:notes, [note_attrs], type: :append)
|> GF.Ash.update!()
Here, the note doesn’t get updated when
note_attrs.id
is an integer. The note only updates when it’s a string.
zachdaniel:
So what likely needs to happen is that we need to cast values to the proper type and use
Ash.Type.equal?
zachdaniel:
Can you open an issue for this? It should be a relatively mechanical change.
zachdaniel:
I think people haven’t encountered this before due to most ash users using UUIDs. We should of course fix it just thinking about how it could possibly have been broken for so long