Realm Flex sync Rules Problem

Hey :slight_smile:

I have problem with realm rules when using realm flex sync.

My document access are based on property « OfficeId » in each document.

I have Office collection who store all office.

I have user collection with array of office object wich contains “role” into the office and “officeId”

When user join one office, by function we had office into array of object of user which include role for this office and officeId.

We would like based our realm rules on this array of object

exemple ( not working ^^)

officeId: $in: user.custom_data.offices.officeId

But after 2 day of test I don’t find anything to do this and I don’t want change my database model for tech limitation …

Thx

Hi @Maxime_Boceno,

I just want to confirm that I’m understanding your data model correctly, it sounds like your custom user data documents look something like this?

{
  "offices": [
    {
      "officeId": 1
    },
    {
      "officeId": 2
    }
  ]
}

If so, then you’re correct that your current permissioning scheme will not work since the $in operator expects an array, so you’ll need to flatten the field to be an array of IDs instead, like so:

{
  "offices": [1, 2]
}

@Maxime_Boceno

I’m rewriting this in C# right now, but here’s the solution to this in React Native:

Your custom data documents contain an array of office objects, each with an “officeId” property, and to my understanding the goal is to use this array to determine document access in Realm Flex Sync. The current attempt to use “$in: user.custom_data.offices.officeId” as a Realm rule is not working because the “$in” operator expects an array of IDs.

The suggested solution is to flatten the “offices” field to be an array of IDs instead of an array of objects. This can be done by changing the user data model to look like this:

{
  "offices": [1, 2]
}

To implement this solution in React Native for iOS, you can use the Realm SDK for React Native to read and update user data. Here is some example code:

import Realm from 'realm';

// Define the User schema
const UserSchema = {
  name: 'User',
  properties: {
    _id: 'objectId',
    offices: 'int[]',
  },
};

// Open a Realm with the User schema
const realm = await Realm.open({
  schema: [UserSchema],
  sync: {
    user: user,
    partitionValue: 'somePartitionValue',
    existingRealmFileBehavior: 'openLocal',
  },
});

// Update a user's offices array
realm.write(() => {
  const user = realm.objectForPrimaryKey('User', 'someUserId');
  user.offices = [1, 2];
});

// Query for all documents where OfficeId is in the user's offices array
const results = realm.objects('SomeCollection').filtered('OfficeId IN $0', [1, 2]);

This code opens a Realm with a User schema that includes an “offices” field of type “int” (an array of integers). It then updates a user’s “offices” array and queries for all documents in “SomeCollection” where the “OfficeId” field is in the user’s “offices” array.

I’ll update in about a half hour or so with another explanation of how to implement this in C# to give you a more clear idea of how this works from different angles.

Here’s the update with csharp, I kept screwing up the error handling as it wouldn’t compile right because my IDE kept trying to pull from another project I have running.

// Define your Office class to match the structure of your Office documents
public class Office : RealmObject
{
    [PrimaryKey]
    [MapTo("_id")]
    public ObjectId Id { get; set; }

    [MapTo("officeId")]
    public int OfficeId { get; set; }

    [MapTo("name")]
    public string Name { get; set; }
}

// Create a MongoDB Realm client
var realmApp = new App(new AppConfiguration
{
    AppId = "my-app-id",
    JwtAuthProvider = new JwtAuthProviderClient(JwtAuthProviderClient.DefaultScheme, () => "my-jwt-token")
});

// Log in to the app using username/password
var user = await realmApp.LogInAsync(new Credentials.UsernamePassword("user@example.com", "password"));

// Get the user's custom data
var customUserData = user.GetCustomData();

// Get the array of office IDs from the user's custom data
var officeIds = customUserData["offices"].AsBsonArray.Select(office => office["officeId"].ToInt32());

// Configure flexible sync
var syncConfig = new SyncConfiguration("my-partition-value")
{
    BsonSchema = RealmSchemas.Register<Office>()
};
var syncClient = realmApp.GetSyncClient(user);
var syncSession = await syncClient.StartSessionAsync();
var syncRealm = await syncClient.OpenRealmAsync(syncConfig);
var officeCollection = syncRealm.GetCollection<Office>();

// Sync the Office collection
var query = officeCollection.Where(office => officeIds.Contains(office.OfficeId));
var subscription = query.Subscribe();
var result = await subscription.WaitForCompletionAsync();

// Handle errors
if (result.ErrorCode == ErrorCode.SessionClosed) 
{
    // Handle session closed
}
else if (result.ErrorCode == ErrorCode.ClientReset) 
{
    var clientReset = result.ClientReset;
    // Handle the client reset
    if (clientReset.InitiatedBy == ClientReset.InitiatedByServer)
    {
        // Delete any unsynced changes
        var unsyncedChanges = clientReset.GetRecoveredChanges<Office>();
        foreach (var change in unsyncedChanges)
        {
            officeCollection.Write(() =>
            {
                if (change.OperationType == ChangeOperationType.Delete)
                {
                    var obj = officeCollection.Find(change.DocumentKey);
                    obj?.Delete();
                }
                else
                {
                    var bsonDoc = change.FullDocument;
                    officeCollection.Add(bsonDoc.ToObject<Office>());
                }
            });
        }
        // Restart the sync
        await syncSession.EndSessionAsync();
        syncRealm.Dispose();
        syncRealm = await syncClient.OpenRealmAsync(syncConfig);
        officeCollection = syncRealm.GetCollection<Office>();
        query = officeCollection.Where(office => officeIds.Contains(office.OfficeId));
        subscription = query.Subscribe();
        result = await subscription.WaitForCompletionAsync();
    }
}

// Access the synced Office documents
var offices = officeCollection.ToList();

// End the sync session
await syncSession.EndSessionAsync();

NOTE: The updated code checks for the SessionClosed error in addition to the ClientReset error. In the case of a ClientReset, the code now deletes any unsynced changes and restarts the sync. The updated code also uses a try...catch block around the code that accesses the synced officeCollection to handle any exceptions that might occur.

I discovered an issue while building a C# app with MAUI and other services that use ML.Net. The ML Pipelines in ML.Net seem to call something in the Realm SDK, which causes compilation and launch errors. While the code looks good, there appears to be a conflict between Realm’s CRL and error handlers and ML.Net. I am not sure if this is a problem with ML Net or the Realm SDK, but it’s an interesting finding to keep in mind.

ML dot Net even tries to run the self healing, but Realm SDK or it just fight each other. It was really odd, but anyways the above code should work.

Hello @Maxime_Boceno,

Were you able to find a solution to your issue? Once you confirm, I will mark the topic as resolved.

Cheers,
Henna