Help understand Rules - Using TaskApp Tutorial

Dear All,
I’m happy to be a new member of the community, this platform seems to have exactly what I’m planning for my app. Currently I’m trying to understand the how mongoldb works.
I started with the IOS tutorial of creating a TaskApp with the ability to have common project etc.
I made the app work as per the instructions, great!

Now following my future goals, I started to modify part of the project and mongobd backend, as follows:

Mongoldb:

Functions:
[modified] - createUserDocument - to make user able to change it own fields(to add new project)

exports = async function createNewUserDocument({user}) {
  const cluster = context.services.get("mongodb-atlas");
  const users = cluster.db("tracker").collection("User");
  return users.insertOne({
    _id: user.id,
    _partition: `user=${user.id}`,
    name: user.data.email,
    canReadPartitions: [],
    canWritePartitions: [`user=${user.id}`,`project=${user.id}`],
    memberOf: [
      {"name": "My Project", "partition": `project=${user.id}`}
    ],
  });
};

[added] - addWriteRulesOnNewProject

exports = async function(projectPartition) {

const collection = context.services.get(“mongodb-atlas”).db(“tracker”).collection(“User”);
const localUserID = context.user.id
if (localUserID == null) {
return {error: No userID found};
}
if (collection == null) {
return {error: No user collection};
}
if (projectPartition == null) {
return {error: No project partition};
}

try {
return await collection.updateOne(

    {_id: localUserID},
    {$addToSet: { canWritePartitions: projectPartition, canReadPartitions: projectPartition }}
);

} catch (error) {
return {error: error.toString()};
}

};

Swift Tutorial TaskProject

[added] - in ProjectViewController @ viewdidLoad a right item “add”
// NEW//////////////

navigationItem.rightBarButtonItem = UIBarButtonItem(title: “Add”, style: .plain, target: self , action: #selector (addProject))

[added] - in ProjectViewController - function addProject

@objc func addProject(){

try! userRealm.write() {
    let project = Project(partition: "project=\(ObjectId.generate().stringValue)", name: "New Project created")
    guard let user = self.userRealm.object(ofType: User.self, forPrimaryKey: app.currentUser?.id) else { print ("no users"); return}
    user.memberOf.append(project)
    //Add projectpartition to users canwrite and canread array
    addProjectRulesToUser(projectPartition: project.partition!)
}

}
func addProjectRulesToUser(projectPartition: String) {
print(“Adding project: (projectPartition)”)
let user = app.currentUser!
user.functions.addProjectRules([AnyBSON(projectPartition)], self.onNewProjectOperationComplete)
}

Results:

Issue:

  • If tapping on the newly created project app crashes with:
    Swift:
    Error Domain=io.realm.unknown Code=89 “Operation canceled” UserInfo={Category=realm.basic_system, NSLocalizedDescription=Operation canceled, Error Code=89}: file Task_Tracker/ProjectsViewController.swift, line 153
    MongodbLog:

mongo db log :

Now I stop the app, restart the app and tap again and it loads it, but if I create again a new one and tap it, it again crashes at the Realm.async when trying to download the realm locally, I guess.

What is wrong here?

Thank you!
I love the platform, cannot wait to deploy the real app!

Hi, thanks for your question!

It’s a bit hard to tell, but my first thought is that you might be sending the project’s partition, which is project=xyz, in addProjectRulesToUser(). But the function addWriteRulesOnNewProject() seems to prepend another project= to the partition key string, resulting in project=project=xyz. Is it possible to log exactly what you’re receiving in the addWriteRulesOnNewProject() function?

Hope this helps!

2 Likes

Thank you for the response, unfortunately i fixed it just before uploading here but did not update the function, at first it was adding project=project=, i saw it and fixed.The final data you can see in results, it is as per Tutorial, only difference that projects are added dynamically.
So to analyze the logic:

  • I add new project to the MemberOf array;
  • The function addWriteRulesOnNewProject(project partition) is called and adds the newly created project to canwrite array of the current user;
  • The new project is displayed on the tableview;
  • On tap om the project, app crashes with the above error and mongodb realm is logging the above error.

As per Mongodb Realm documentation, it says if the realm will be opened for the first time, you need to call Real.async, which is also already implemented in the tutorial.
So considering that before opening the new realm, the user has all necessary rights and permissions to open the realm, the mongodb Realm is still logging the permission error (sorry for repeating).

Not certain that it would cause this problem, but I see that you’ve removed canReadPartitions: [user=${user.id}] from createNewUserDocument - try adding that back in as it may mean that you don’t have the correct user data.

If that doesn’t fix it, then I’d focus on the Realm log that’s reporting that the user doesn’t have permission to sync partition “project=5ff…cc103”.

The test to see whether the the user can sync a partition or not is decided by the functions canReadPartition and canWritePartition.

Have you made any changes to those functions?

Try returning true from the functions to confirm that that’s what’s triggering the error.

Tracing what’s happening in those functions is a little messy but can be done. Add console.log commands to print out things like the name of the function, the user’s ID, whether an if test passes, etc. – the outputs will appear in the Realm logs.

Let us know how you get on!

1 Like

Hi Radu,

Just an addition to the above, I know you’re modifying your own project after getting the tutorial to work, but if you’re looking for a further reference, beyond the docs, and an alternative approach, @Andrew_Morgan who replied also, recently posted a variation on the Tutorial using SwiftUI & Combine - you can read this in our Developer hub HERE.

Hey Andrew,
I added user [user=${user.id}] back to the canread, but nothing changed.
Current form:

No changes to the canRead or canWrite functions.

I just changed the permissions on the sync to only true, bypassing completely the canRead/canWrite functions and I still get same error on mongoldb and the app crashes. It’s like the problem is in the Realm.async, not being able to attempt to download or create the realm.

Other ideas ? :frowning:

Thank you Shane, I’m going to take a look. I’m still going to work with the UiKit, at least for now, but maybe in the future.

I uploaded the CLI and swift file on my git hub, maybe it will be easier to troubleshoot.
Project Git-Hub Link

Building it now - I’ll let you know what I find

Hi Radu,

I’ve found a solution.

As I’d expected, the problem is that canReadPartition and/or canWritePartition are returning false when they should return true. Making them both simple return true (and hitting Deploy) got things working.

The issue is that the functions use the current user’s custom data object to see if the partition is listed in there. Changes to custom data only appear when the user refreshes their token (e.g. when logging back into the app).

So, on creating a new project, you correctly call your function to add the associated partition to the User collection. When the user then tries to open the project (and your code tries to open the realm), the canXxxxPartition functions are called but they make their decision on “stale” custom user data. When you restart the app, the user has to login again -> refresh of user token -> custom data updated.

The fix is for the functions to fetch the User document from the database (rather than relying on the (possibly) stale custom user data).

This is the working canReadPartition function:

exports = async function(partitionValue) {
  const cluster = context.services.get("mongodb-atlas");
  const userCollection = cluster.db("tracker").collection("User");
  
  return userCollection.findOne({ _id: context.user.id })
  .then (userDoc => {
    return userDoc.canReadPartitions && userDoc.canReadPartitions.includes(partitionValue);
  }, error => {
    console.log(`Couldn't find user ${context.user.id}: ${error}`);
    return false
  })
}

and the working canWritePartition function:

exports = async function(partitionValue) {
  const cluster = context.services.get("mongodb-atlas");
  const userCollection = cluster.db("tracker").collection("User");

  return userCollection.findOne({ _id: context.user.id })
  .then (userDoc => {
    return userDoc.canWritePartitions && userDoc.canWritePartitions.includes(partitionValue);
  }, error => {
    console.log(`Couldn't find user ${context.user.id}: ${error}`);
    return false
  })
};

Please let me know if this works for you.

2 Likes

It really was the solution!

But why when I changes the app Sync rule to both true it was not working ?

Did you hit the “Deploy” button (and possibly wait for a couple of seconds)?

(Whenever I create a new Realm app, the first thing I do is disable drafts under Deploy/Configuration as it’s so easy to miss the prompt to manually deploy)

Thanks @Radu_Patrascu for raising this – we’ll update the code in the tutorial so that others don’t hit similar problems when they try extending the mobile apps.

1 Like

I think I did, deployed it, but hard to say now, I’ll try tomorrow to break it again :smiley: and check.

Thank you very much, it was really painful to try fixing it myself.

Where exactly can I find this info about the difference between context.user and a queried user and other traps like this ? :slight_smile:

You can start here… https://docs.mongodb.com/realm/users/enable-custom-user-data

I use it in frontend apps as a convenient read-only snapshot of what the data looked like when the user last logged in. If I need the data to be 100% up to date (rather than up to 30 minutes stale) then I’d read it from the database.

In general, I don’t use it in my backend functions as it’s so easy and cheap to fetch the completely up to date data from the database each time.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.