.findOneAndUpdate() Realm Embedded Object with Function

Again I’m running into an issue where I just don’t quite understand how to do very Realm-y things in the Realm functions. Hoping for some better documentation soon.

I have a trigger that fires when a user updates their name, which is in their User object in their “user=User_id” partition.

So the change event is the user object with the updated name.

@Andrew_Morgan helped me in another forum post see how to add a Hunter object to my Hunt realm object here:

In my function I am able to generate the _id of the embedded object which is lives in Hunt.hunters.

I have attempted the following, but can’t seem to get the syntax right:

const hunterQuery = { "hunters": { "_id": idCode }};
const hunterUpdate = {
          "$set": {
            "hunter.name": result.name,
            "hunter.email": result.email
          }
        };
const huntOptions = { returnNewDocument: true };
huntsCollection.findOneAndUpdate(hunterQuery, hunterUpdate, huntOptions);

I have console logged and verified that the idCode is correct for the embedded object, but I’m assuming that I either need to adjust the hunterQuery or the hunterUpdate in order to write to the embedded object.

This is what the embedded object looks like in Atlas:

Any pointers or even where to find any info about Realm Functions and Embedded Objects would be awesome. Thanks!

–Kurt

Hi Kurt,

as hunters is an array, you should use $push to add the new hunter. This code works for me:

exports = function() {
  const db = context.services.get("mongodb-atlas").db("RChat");
  const huntsCollection = db.collection("Hunts");
  
  const idCode = "123";
  const result = {
    name: "Bily",
    email: "billy@contoso.com",
    otherStuff: "Not interested in this"
  }

  const hunterQuery = { "_id": idCode };
    const hunterUpdate = {
          "$push": {
            hunters: {
              name: result.name,
              email: result.email
            }
          }
        };
  const huntOptions = { returnNewDocument: true };
  
  return huntsCollection.findOneAndUpdate(hunterQuery, hunterUpdate, huntOptions)
  .then(result => {
    console.log(`Updated Hunts doc for ${result._id}`);
  }, error => {
    console.log(`Failed to insert User document: ${error}`);
  });
};

Thanks @Andrew_Morgan.

I’ve attempted to use the $post instead of $set and while that seems more appropriate because they are in an array, it’s still not working.

Just to be clear, I have a huntId and a hunterId.

The hunterQuery is looking for a member of hunt.hunters with the _id == hunterId

so in the huntsCollection.findOneAndUpdate, is it looking at the hunt._id or the hunt.hunters._id?

I was able to find the correct embedded object to update by using

const hunterQuery = { "hunters._id": idCode };

the problem is that it pushed a new object into the array rather than finding the correct properties and updating those with the new result.name and result.email.

I only want to update the name and email properties in the existing embedded object in the array. My understanding is that $set will only update the included properties and leave the rest, like your otherStuff property, intact.

I assume that is what this is trying to do, but for me, $push is creating a new Hunter object rather than finding and updating the existing embedded object. I did not set upsert to false, though I assume that not setting it at all would default to false.

OK, I think I now understand what you’re trying to do – update an existing sub-document within an array.

If I’m right then you need to use the $ positional operator to index into that array (it should match with the first element that matched your query):

exports = function() {
  const db = context.services.get("mongodb-atlas").db("RChat");
  const huntsCollection = db.collection("Hunts");
  
  const idCode = "321";
  const result = {
    name: "Billy",
    email: "billy@contoso.com",
    otherStuff: "Not interested in this"
  }

  const hunterQuery = { hunters: { "_id": idCode }};
  const hunterUpdate = {
    $set: {
      "hunters.$.name": result.name,
      "hunters.$.email": result.email
    }
  };
  const huntOptions = { returnNewDocument: true };
  
  return huntsCollection.findOneAndUpdate(hunterQuery, hunterUpdate, huntOptions)
  .then(result => {
    console.log(`Updated Hunts doc for ${result._id}`);
  }, error => {
    console.log(`Failed to insert User document: ${error}`);
  });
};

Thinking some more, I wonder whether your app might actually want to update all Hunts that this hunter belongs too. In which case, you’d want to use updateManay rather than findOneAndUpdate:

exports = function() {
  const db = context.services.get("mongodb-atlas").db("RChat");
  const huntsCollection = db.collection("Hunts");
  
  const idCode = "321";
  const result = {
    name: "Billy",
    email: "billy@contoso.com",
    otherStuff: "Not interested in this"
  }

  const hunterQuery = { hunters: { "_id": idCode }};
  const hunterUpdate = {
    $set: {
      "hunters.$.name": result.name,
      "hunters.$.email": result.email
    }
  };
  const huntOptions = { returnNewDocument: true };
  
  return huntsCollection.updateMany(hunterQuery, hunterUpdate, huntOptions)
  .then(result => {
    console.log(`Updated Hunts docs`);
    // console.log(`Updated Hunts doc for ${result._id}`);
  }, error => {
    console.log(`Failed to insert User document: ${error}`);
  });
};
1 Like

Thanks @Andrew_Morgan! That is exactly what I needed.

I hadn’t seen the $ positional operator for use with embedded objects. Maybe it’s because I’m searching with Realm lingo in MongoDB documentation and it’s still a bit wonky to translate between the two.

I definitely thought I had to get more granular with my server functions because I’m used to Realms/Partitions in the mobile SDKs, so recognizing the ability to query across partitions for all objects is extremely powerful and probably not emphasized enough.

I had seen .updateMany() and attempted it’s use a bit, but seeing it here in this example and then how much it simplified my code is :exploding_head:. Truly amazing.

Is there a guide or documentation for the equivalent of what would be dealing with Realm embedded objects / embedded objects in Realm lists when writing Realm functions?

I have been searching the documentation and guides and hadn’t seen this and would love to see more of what might be possible.

Thanks again. -Kurt

Hi @Kurt_Libby1, glad this works!

Most of the syntax you use to access the database in Realm Functions is actually the same as the MongoDB Node.js driver. In Realm functions, you’re working with (MongoDB) documents and sub-documents rather than Realm Objects and Embedded Objects. That means the best tip is to drop “realm function” from your Google search and add “mongodb javascript” instead.

2 Likes

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