Idan
Idan

Reputation: 432

Editing an array element nested inside an array element mongo

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!

Upvotes: 2

Views: 69

Answers (4)

DanJHill
DanJHill

Reputation: 177

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.

Upvotes: 0

Shrabanee
Shrabanee

Reputation: 2766

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);
    }
  });
}

Upvotes: 1

Idan
Idan

Reputation: 432

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);
}

Upvotes: 0

Amiram Korach
Amiram Korach

Reputation: 13286

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.

Upvotes: 1

Related Questions