1

First of all, let me start by providing my MongoDB schema.

showTitle: String,
seasons: [{
  seasonNumber: String,
  episodes: [{
      episodeNumber: String,
      episodeTitle: String,
      episodeSynopsis: String
            }]  
         }]

The basic idea for this collection is to store tv show details. The TV shows can contain multiple seasons and each seasons contain multiple episodes.

I am allowing users to add additional seasons(only seasonNumber) at the client side. This will pass a value to the server side. This part works just fine as I can view the value, a string when I console.log at my server side.

Here, my API calls this particular function.

function saveReport(err, res, count, seasonId, seasonDetails)
{
  if(count == 0) //if 0 means not exist, so can add into DB
  {
    Show.update({_id: seasonId},{$push: {seasons:{seasonNumber: [seasonDetails]}}}, {upsert:true}, function(err, result)
    {
      console.log(result);
      res.json(result);
    });
  }
  else
  {
    res.json("TV Season already exists in MongoDB");
  }
}

module.exports.seasonsCreate = function(req, res)
{
  console.log("Calling the seasonsCreate function");
  var seasonId = req.params.id;
  var seasonDetails = req.body.season; //season number as a string(intended)

 //finding a specific showing using the id passed as parameter
 //$elemMatch is used to check if season number exists
 //using count to determine
  Show.findOne({_id: req.params.id, seasons: {$elemMatch: {seasonNumber: req.body.season}}}).count(function(err, count)
  {
    saveReport(err, res, count, seasonId, seasonDetails);
  });
}

I manually(using MongoDB command) added two seasons into MongoDB. Season 1 and Season 2 with 2 episodes each. However, when I try to add a 3rd episode via the client side, nothing happens. The exact result being returned is this:

Object {ok: 0, n: 0, nModified: 0}

I've done updating before using a similar method. However, I'm a bit thrown off because this time I have nested arrays instead of just objects. I've also tried several combinations:

  • with/without push
  • with/without set
  • with/without upsert

I'm pretty sure the problem is the way I am updating my database. Hope someone can shed some light here. Thanks !!

p/s: I'm using latest version of Mongoose.

9
  • That's a nested array. So the catch is you need to find "which" element of the outer array matches the to the one you want to update the inner array in. It's possible with $push, but other update operations are not so straightforward. I would strongly suggest to not use a nested array and figure a way to model differently. Commented Jun 11, 2015 at 8:36
  • @user3561036 do you have any suggestions on how I should model my schema then? I am new to this so any reading links would be helpful as well in terms of defining schema. Commented Jun 11, 2015 at 8:39
  • 1
    @B3rn475 Because that sucks in a nutshell. You are reading an object from the database, making changes offline and then writing it back. Leads to data inconsistency everywhere. Commented Jun 11, 2015 at 8:44
  • 1
    @user3561036 It is not different from what is actually happening, no one garantees you that nothing happens between the count and the update. Starting from your assumption no ORM o ODM should be used at all. So just remove mongoose and go for plain MongoDB Connection Commented Jun 11, 2015 at 8:46
  • 1
    @B3rn475 It's worlds of difference. $push will append to whatever content is in the array on update. What you are suggesting will overwrite any changes that were issued in between the .find() and the .save() Commented Jun 11, 2015 at 8:48

1 Answer 1

4

Since I'm sensing dangerously bad advice issuing from people telling you to .find() the data then .save() it I'll explain the basic case of "flattening" the data in order to avoid the nested array issue.

Nested arrays suffer from a problem that the positional $ operator you would need to use to match the index of the required array element to update can only reference the "outer" array element. While this might be fine to identify that element and "push" to an inner array of that element, it is just not possible to get the position of the inner element if you wanted to update that inner element.

So the idea here is to "not nest arrays", and structure so you can actually efficiently update within the available constraints.

A better structure would be:

showTitle: String,
episodes: [{
  seasonNumber: String,
  episodeNumber: String,
  episodeTitle: String,
  episodeSynopsis: String
}]

Now you don't really loose anything here, and it's just a little extra duplication of data, but at a small cost.

To add to the array:

Show.update({ "_id": showId },{ "$push": { "episodes": episodeData } },callback)

To update an element:

Show.update(
    { 
       "_id": showId,
       "episodes": { 
           "$elemMatch": {
               "seasonNumber": season,
               "episodeNumber": episode
           }
       }
    },
    { "$set": { "episodes.$": episodeData } },
    callback
)

Or even:

Show.update(
    { 
       "_id": showId,
       "episodes": { 
           "$elemMatch": {
               "seasonNumber": season,
               "episodeNumber": episode
           }
       }
    },
    { "$set": { "episodes.$.episodeSynopsis": synopis } },
    callback
)

And if you just wanted all episodes for a season:

Show.aggregate(
    [
        { "$match": { "_id": showId, "epsisodes.seasonNumber": seasonNumber } },
        { "$redact": {
            "if": { 
                "$eq": [
                    { "$IfNull": [ "$seasonNumber", seasonNumber ] } },
                    seasonNumber
                ]
            },
            "then": "$DESCEND",
            "else": "$PRUNE"
        }}
    ],
    callback
)

Which will cull any unmatched entries from the array as it is returned in the query.

So you can do just about everything you want with little fuss in changing the data storage, and with atomic updates that cannot run into problems with other operations changing the data into an unexpected state as you run the risk from otherwise.

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

3 Comments

As a complement to this excellent clarification, worth mentioning the SERVER-831 proposal to enhance the positional operator $ for nested arrays.
@SylvainLeroux I'd applaud that except it is issue 831 so not exactly a new request. Nonetheless it can't hurt for more people up-voting that issue. Is there a magic number of up-votes that make this a top priority :)
This will definitely solve all my future problems. Thanks @user3561036 !!

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.