How to replace all connections in a many-to-many relationship in Fauna

Viewed 347

Say I have this schema:

Client {
 services: [Service] @relation(name: "ClientServices")
}
Service {
 clients: [Client] @relation(name: "ClientServices")
}

And I want to connect some services to a client, like this:

mutation {
 updateClient(id: ..., data : {
   services:  {
     connect: ["123", "456"] ## where there are valid service _id's
  }
 }) { ... }
}

Fantastic. Works great. But now I want to replace all the connections (services) with other ones, but still keep the 123 service.

mutation {
 updateClient(id: ..., data : {
   services:  {
     connect: ["123", "789"] ## notice kept 123, and now want 789
  }
 }) { ... }
}

This will result in a instance not unique error from Fauna. Because its trying to connect 123 but its already in the ClientServices collection (the auto-generated collection that manages m-m relationships.)

Is there an easy way to disconnect all and reconnect the ones I want in one call without a UDF?

2 Answers

At this point, Fauna has create, disconnect and connect. You seem to be looking for a replace that automatically calculates a diff and takes the correct actions in terms of disconnect and connect. I don't think that's currently possible with pure GraphQL. You would indeed need to write a UDF. It might be an interesting feature for others as well so it could be a good candidate for a feature request on the forums: https://forums.fauna.com/

I believe you can connect and disconnect in the same mutation (without a UDF):

mutation {
 updateClient(id: ..., data : {
   services:  {
     connect: ["789"],
     disconnect: ["456"]
  }
 }) { ... }
}

This assumes you have the ability to compute the diff on the client. For example, using lodash:

const _filter = require('lodash/filter');
const _find = require('lodash/find');
const _map = require('lodash/map');

let orig = [{id: "123"}, {id: "456"}]
let replacements = [{id: "123"}, {id: "789"}]

let toConnect = _filter(replacements, o => !_find(orig, {id: o.id}))
let toDisconnect = _filter(orig, o => !_find(replacements, {id: o.id}))

mutate(
    // ...
    {
        connect: _map(toConnect, 'id'),
        disconnect: _map(toDisconnect, 'id')
    },
    // ...
)

As an easy solution for providing replace functionality, maybe the Fauna GQL API could provide some guarantees around order of operations. If the disconnect is always performed before the connect, then you could always disconnect orig and connect replacements. This guarantee seems reasonable, since I can't really imagine a sane reason for connecting and immediately disconnecting a relation in the same API call.

Related