Flexible Sync on links

I was wondering how to work with object-links and Flexible Sync.
Considering one very simple example:

@RealmModel()
class _Pet {
  ...
}

@RealmModel()
class _Person {
  List<_Pet> pets = [];
}

In this case, I cannot create a query on the pets of a person, meaning I can not query them with Device Sync. My idea was to use ObjectId instead of the real reference:

@RealmModel()
class _Person {
  List<ObjectId> pets = [];
}

But now I am basically losing the real connection to the database and am required to query the objects manually.

Is there a proposed solution for this scenario as it seems very common to me.

I think the easiest solution, if you have a sufficiently small data set, is to subscribe to all Person objects and all Pet objects. Then, you know the object links will always work.

If you have sufficiently restricted permissions model, such as the read and write own data model proposed in the Flexible Sync Permissions Guide, you can sync on a field that must be in all objects the user can access - i.e. ownerId == user.id. Then both linked objects would need the ownerId field, but the link would be preserved.

Otherwise, you’d probably want to look for common data patterns to sync on. For example, maybe your Pet and Person both have a region field, and you could sync all Pet and Person objects within a region.

Curious if any folks have other suggestions for how they’re handling this.

4 Likes

Thank you for your input. But if I add a field “ownerId”, it is of type ObjectId and no longer of the original object type.

I thought about having both, a real object reference and an objectId reference, but this seems very wrong.

@Thomas_Anderl did you get this worked thru? I just went thru implementing similar to Dachary’s suggestion and can share my experience if you need.

Dachary’s suggestion is the implementation I settled on.

Hey, @Joseph_Bittman.

I would be happy if you could share your solution. I am not 100% sure if I fully got the idea.

1 Like

@Thomas_Anderl, are you using flexible sync? That is what I am using. I can share soon.

Here is some better documentation from another SDK (I’ve found some SDK flavors have more detailed documentation on specific topics).

In the “to-many” section example:

const Person = {
  name: "Person",
  properties: {
    name: "string",
    birthdate: "date",
    dogs: "Dog[]"
  }
};
const Dog = {
  name: "Dog",
  properties: {
    name: "string",
    age: "int",
    breed: "string?"
  }
};

Again, this is the object model in the SDK, and your SDK syntax will be a bit different, but you want essentially to use this object model example and translate it to yours.

Regardless of the SDK you use this with, here is the experience you will get:
Each person instance will be in its own collection.
Each dog instance will be in its own collection.

personInstance.dogs will be an array of RealmObjects
personInstance.dogs[n] will be an actual, fully fleshed out data object. You don’t have to manually query them to resolve them from object ids. Also, each realmobject will have an id value in case you want the object id.

You can do things like:
personInstance.dogs[n].name = “new doggie name”
And this will persist back to the instance of the dog in the dogs collection. The person instance in the person collection just contains a reference (that is resolved automatically for you) to the instance in the dog collection.

Also, with flexible sync, you don’t need to pull every person or dog in the collections for this to work. If you use a subscription, you can pass a query/filter/where syntax to pull down just the Person instances (and dogs as applicable) that are relevant. This is what Dachary was referencing:

 let config = user.flexibleSyncConfiguration(initialSubscriptions: { subs in

                    subs.append(QuerySubscription<ItemGroup>(name: "user_groups") {
                        $0.ownerId == user.id
                    })
                    subs.append(QuerySubscription<Item>(name: "user_items") {
                        $0.ownerId == user.id
                    })
                }

In this example from the swift-ui-tutorial link above, in your case, “user_groups” is Person and “user_items” is dogs. You need a subscription to each collection, and can filter down to some more limited dataset as appropriate. “ownerId” would have been set up as a “queryable” field.

Make sure you have set permissions properly on the server’s collection permissions and not just filtering in the end client interface.

Again, check out some subscription articles from various SDKs to get a more complete understanding of how subscriptions are handled.

I was able to track down this documentation, so I think this is more helpful than my implementation as it will allow you to discover more answers to questions around these topics. I basically did what is above where I have a queryable field by ownerId, and subscriptions to both collections, and then can interact with the data objects without having to manually resolve the references.

Hope this helps!

1 Like

@Joseph_Bittman Thank you for your response. The subscription is however the part I struggle with. The swift-example you are refering to uses

$0.ownerId == user.id

So here, we have a field called ownerId which is an ObjectId and not an Object. That means, this QuickStart does also not use real objects. For a field to be queryable, it cannot be a link if I understood correctly. So to your example to work, dog needs to be extended:

const Dog = {
  name: "Dog",
  properties: {
    name: "string",
    age: "int",
    breed: "string?",
    ownerId: "objectId" //NEW
  }
};

And this is where my problem starts. I don’t want to model all references by objectId just because I need to query them. I would prefer having

owner: ‘Person’

which would be the “natural” way to solve this, but then I cannot query the field anymore.

I see. Yes, you are right.

As you said, it would be more natural to refer to, in some cases, an object than an object id. MongoDB historically, clearly, has not really enabled this as demonstrated from the partition key method of permissioning user data… or defining your own field to have an id to then use when applying permissions.

Flexible sync has added additional roles/permission rules but still doesn’t support permissions (or subscriptions) to be evaluated based on embedded/linked/referenced objects.

Their documentation still examples using ownerId, which is objectid as you have pointed out.

I guess the difference between my use cases and yours is that I don’t really go from any particular object to its owner often. Therefore, I can use the object id and work with it in permission rules and subscriptions without inconvenience. In the infrequent cases where I need the actual user data (such as in Account), then I’m just querying for the specific user. Hence being able to implement similar to the examples and Dachary’s suggestion.

@Joseph_Bittman

Thank you. This solution is the one I took now. But it feels wrong to me. Wanted to make sure I wasnt missing anything here.

Doing it that way just adds an unnecessary layer of complexety and inconsistenty for the developers and results in unforseeable issues (e.g. GraphQL won’t work the way it is supposed to anymore)

It may not be optimal, but you could have an ownerId property on both objects that is not the object link. For example, person could still have the list of dogs to represent the to-many relationship, and both the person and dog objects have an owner id. So you’d only be syncing objects where ownerId matches the user.id, but the ownerId isn’t actually the linking field - the relationship field is the linking field as you’d expect. In this case, ownerId is just a piece of metadata that allows you to sync the objects you want. This gives you some future-proofing for when linked objects are supported; you’ll already have the relationship field so you’ll have the natural link, and can just start ignoring the ownerId field at that point.

@Dachary_Carey I thought about this too. But this only covers this specific use case. I also have a chat in my application and there I need a different approach (saving a copy of the chatIds in the user metadata and the messages referencing the chat via objectId instead of a link).

There is a workaround for all scenarios but I was hoping there was a cleaner way from MongoDb to deal with that issue. Seem to be kind of nasty workarounds to me.