ChickenWing24
ChickenWing24

Reputation: 205

Add elements to an array in MongoDB using Mongoose

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:

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.

Upvotes: 1

Views: 7135

Answers (1)

user3561036
user3561036

Reputation:

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.

Upvotes: 4

Related Questions