Bidirectional `manage_relationship`

Is there a way to get manage_relationship to manage the other side of the relationship simultaneously? Specifying destination_relationship: :other etc.
26 Replies
ZachDaniel
ZachDaniel3y ago
manage_relationship calls actions on the destination resource, and by default those are the primary actions on the resource, but it can be changed by changing your options to manage_relationship (see the docs in Ash.Changeset module for manage_relationship. If you have those actions also call manage_relationship then they can do what they want
\ ឵឵឵
\ ឵឵឵OP3y ago
Ok, so if my primary create action on the destination resource does something like:
actions do
create :create do
primary? true
argument :other, App.Other
change manage_relationship(:other, type: :append)
end
end
actions do
create :create do
primary? true
argument :other, App.Other
change manage_relationship(:other, type: :append)
end
end
How does it know which argument to provide to the related resource's create action?
ZachDaniel
ZachDaniel3y ago
The input is provided nested
argument :relationship, :map

change manage_relationship(:relationship, ....)

%{relationship: %{other: %{a: 1}}}
argument :relationship, :map

change manage_relationship(:relationship, ....)

%{relationship: %{other: %{a: 1}}}
If the action on relationship managed :other
\ ឵឵឵
\ ឵឵឵OP3y ago
This looks like it's for nested relationships.
ZachDaniel
ZachDaniel3y ago
Ah, I guess I don't know what you mean about bidirectional then
\ ឵឵឵
\ ឵឵឵OP3y ago
Populating the reverse relationship on the related resource.
ZachDaniel
ZachDaniel3y ago
ah, I see. Nothing does that currently You can add an after_action hook to do it manually if you wanted That may or may not work
\ ឵឵឵
\ ឵឵឵OP3y ago
other = %{id: Changeset.get_attribute(cs, :id)}
other = %{id: Changeset.get_attribute(cs, :id)}
ZachDaniel
ZachDaniel3y ago
Could you show me a real example of what operation you're trying to do? and what you'd like to see returned for example
\ ឵឵឵
\ ឵឵឵OP3y ago
Sure, sec This is how I'm doing it now, which works:
update :add_other do
argument :other, :map

change fn cs, r ->
self = %{id: Changeset.get_attribute(cs, :id)}

other =
Changeset.get_argument(cs, :other)
|> Map.put(:self, self)

case App.Other.create(other) do
{:ok, other} -> Changeset.manage_relationship(cs, :others, other, type: :create)
err -> err
end
end
end
update :add_other do
argument :other, :map

change fn cs, r ->
self = %{id: Changeset.get_attribute(cs, :id)}

other =
Changeset.get_argument(cs, :other)
|> Map.put(:self, self)

case App.Other.create(other) do
{:ok, other} -> Changeset.manage_relationship(cs, :others, other, type: :create)
err -> err
end
end
end
With the create action above on the target resource. Wondering if there's a shorthand to tell manage_relationsip to populate the relationship on the destination resource with the current resource, e.g. by providing the relationship name on the destination resource, or specifying it in the relationships section, e.g. reverse_relationship.
ZachDaniel
ZachDaniel3y ago
Nothing to do that kind of thing automatically. So on the other resource, you have a manage_relationship call also? It seems like you'd just want to load that data, no need to manage it
\ ឵឵឵
\ ឵឵឵OP3y ago
Currently, yes, for this method.
ZachDaniel
ZachDaniel3y ago
update :add_other do
argument :other, :map
change manage_relationship(:other, :others, ...)

change load(others: :self)
end
update :add_other do
argument :other, :map
change manage_relationship(:other, :others, ...)

change load(others: :self)
end
\ ឵឵឵
\ ឵឵឵OP3y ago
I think load doesn't inject the current resource on the destination?
ZachDaniel
ZachDaniel3y ago
thats what the others: :self does using manage_relationship at all relates the destination things to the current thing you don't have to do that manually it just doesn't load the reverse relationship automatically
\ ឵឵឵
\ ឵឵឵OP3y ago
If I don't add the current resource like this:
other =
Changeset.get_argument(cs, :other)
|> Map.put(:self, self)
other =
Changeset.get_argument(cs, :other)
|> Map.put(:self, self)
I was getting nil in the reverse relationship on the other resource.
ZachDaniel
ZachDaniel3y ago
where were you getting nil? In the changeset?
\ ឵឵឵
\ ឵឵឵OP3y ago
I'm not too worried about it loading the reverse relationship, just creating it. In the resulting created resources.
ZachDaniel
ZachDaniel3y ago
🤔 you were getting nil, not %Ash.NotLoaded{}?
\ ឵឵឵
\ ឵឵឵OP3y ago
Querying them after creation, the reverse relationship was nil.
ZachDaniel
ZachDaniel3y ago
Sounds like potentially a misconfiguration of your relationship fields or something like that To debug further I think I'd need you to reproduce it in a test in ash
\ ឵឵឵
\ ឵឵឵OP3y ago
update :add_other do
argument :other, :map
change manage_relationship(:other, :others, type: :create)
end
update :add_other do
argument :other, :map
change manage_relationship(:other, :others, type: :create)
end
So this should just work the way I was thinking, populating the current resource on the destination relationship during creation?
ZachDaniel
ZachDaniel3y ago
yep! Thats the main purpose of manage_relationship is to create/update/destroy related things (and so creating them would imply making them related on creation) I'd check to make sure the reverse relationship isusing the right keys
\ ឵឵឵
\ ឵឵឵OP3y ago
Great, I had the impression that this was how it was supposed to work 🙂 OOC is it required that there is an argument + manage_relationship call in the default create action of the destination resource?
ZachDaniel
ZachDaniel3y ago
Nope @\ ឵឵឵ did you ever figure this one out?
\ ឵឵឵
\ ឵឵឵OP3y ago
@Zach Daniel I believe so, but I didn't have time to dig into what was going wrong earlier. Tbh the behavior you described was what I was expecting at the start. If it crops up again I'll put together a minimal.

Did you find this page helpful?