2

I have a mongo-based application which displays reports. In each report, users can post comments that are shown to all other users, as well as reply to existing comments. There are two "levels" of comments, the top comments with no "parents", and replies with "parents". Comments are stored in an array "discussions", with each having another array called "replies".

db.reports.findOne()
{
    "_id" : "L154654258",
    "timestamp" : ISODate("2016-03-17T01:15:00Z"),
    "title" : "Server Offline",
    "content" : "A server has become offline due to reason X112",
    "discussions" : [
        {
            "id" : ObjectId("57beb8068d9da75ed44e0ebc"),
            "user_id" : "david",
            "timestamp" : ISODate("2016-03-17T01:15:00Z"),
            "comment" : "Is this the first time that it happens?",
            "last_edit" : null,
            "replies" : [
                {
                    "id" : ObjectId("57beb8068d9da75ed44e0ebd"),
                    "user_id" : "gene",
                    "timestamp" : ISODate("2016-03-17T01:20:00Z"),
                    "comment" : "I have never seen anything like that before",
                    "last_edit" : null
                },
                {
                    "id" : ObjectId("57c480b7ee568ddc634fd977"),
                    "user_id" : "david",
                    "timestamp" : ISODate("2016-03-20T01:20:00Z"),
                    "comment" : "Thanks!",
                    "last_edit" : null
                }
        }
    ]
}

So far using this scheme I was able to deal with inserting new items to each comment (parents and replies), as well as edit the content of parents (if a user chooses to edit their post), using the positional $ operator. My problem is updating replies. I tried this (I assumed it won't work, just wanted to test it out):

db.reports.findOne({'_id': 'L154654258', 'discussions': {'$elemMatch': {'id': ObjectId("57beb8068d9da75ed44e0ebc"), 'replies': {'$elemMatch': {'id': ObjectId("57beb8068d9da75ed44e0ebd")}}}}},
{'$set' : {'discussions.$.replies.$.comment': "This is a test!", 'discussions.$.replies.$.last_edit': 'Fantastic'}}
)

Testing this query with findOne() seems to show that the find part of the query works. However, how do I update an array element of an array? I assume using the $ operator twice won't work, but is there a way to do it or do I need to use Javascript? Thanks!

2
  • 2
    Yep, that's the problem with excessive nesting of arrays. You can't work with them efficiently. In our app, we only allow one level of embedded arrays, for precisely this reason. Commented Aug 31, 2016 at 10:42
  • 1
    Thanks! I was hoping to execute it using javascript (similar to Sharabanee's answer below). It makes sense to have replies nested within the "parent" comments as we want to limit the number of displayed "parent" comments (regardless of how many replies these comments have) per page... Commented Aug 31, 2016 at 12:22

4 Answers 4

1

You can't do it with the $ operator. It will always update the first element. You have to find the document, change it in your server, and save it back to mongo.

Sign up to request clarification or add additional context in comments.

Comments

1

Try something as below:-

var findQuery = {'_id': 'L154654258', 'discussions.replies.id': ObjectId("57beb8068d9da75ed44e0ebd")};

db.restaurants.find(
findQuery).toArray(
function(err, result)
{
    if(err)
    {
         console.log(err);
    }
    else if(result && result.length)
    {
      var index,found = false;
     for(var j in result[0].discussions){
      for(var i in result[0].discussions[j].replies)
      {
         if(results[0].discussions[j].replies[i].id == ObjectId("57beb8068d9da75ed44e0ebd"))
         {
            found = true;
            index = i;
            break;
        }  
     }
   }

   if(found)
   {
        var x = 'discussions.$.replies.'+index+'.last_edit';

        var     updateObject = {$set : {}};    
        updateObject['$set'][x] = 'Fantastic';

        db.restaurants.update(findQuery, updateObject);
    }
  });
}

13 Comments

This is more along the line of what I was searching for. However, for some reason, it doesn't work. It executes (with additional changes, such as changing restaurants to reports), but it just shows the result of the find as an array. no code within function(err, result) actually runs (even if it's just print("hello"!);) Any help would be appreciated!
It went inside first and second for loop?
I know it's bit complicated. But as of now, this is the way that I think you have to use to update the field you want.
I understand the code in the answer, for some reason it just doesn't execute. This is the type of solution I was looking for! It just showed the entire record once with the array brackets [ ] . As noted, just putting print("HELLO") in the toArray function doesn't seem to change anything. Doesn't seem like the content of this function is executed... thanks again!
Try changing the find query. [] this means, there is no result matches the findQuery. Can you just give {'_id': 'L154654258'} as findQuery and check? Is there any record exists or not?
|
0

Based on Sharabanee's code, here is the code that worked for me:

var findQuery = {'_id': 'L154654258', 'discussions.replies.id': ObjectId("57beb8068d9da75ed44e0ebd")};
var result = db.reports.find(findQuery).toArray();
var index,found = false;
for(var j in result[0].discussions) {
    for(var i in result[0].discussions[j].replies) {
         if(result[0].discussions[j].replies[i].id.equals(ObjectId("57beb8068d9da75ed44e0ebd"))) {
            found = true;
            index = i;
            break;
        }  
    }
}
if(found) {
    var x = 'discussions.$.replies.'+index+'.comment';

    var  updateObject = {$set : {}};    
    updateObject['$set'][x] = 'Fantastic';

    db.reports.update(findQuery, updateObject);
}

3 Comments

Totally not atomic and prone to race conditions. This might not be a concern for you. JFYI.
What do you mean by race condition? when a scenario like that happen?
Consider this scenario: Client C1 reads discussion D and starts searching for a reply to update. It finds one at, say, index 3. But before it has a chance to perform update, client C2 modifies the same discussion D so that indexes are invalidated (for example, deletes the first reply). C1, oblivious to this change, proceeds to set the field on reply with index 3, but it's going to be a wrong reply.
0

I know this post is old, but I was just looking myself for how update an element in an array of arrays in Mongo.

It is possible to do purely in Mongo without having to know the position in the array, using arrayFilters thanks to an update, found from v3.6 onwards.

There is another answer detailing how to use this found here.

I hope this helps someone also looking for how to solve this problem without race-conditions introduced using code outside of Mongo.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.