0

I'm learning MEAN stack applications and am working on a tv watchlist app. I built the api successfully without the seasons field in the model. Now I'd like to add this field so that a user can add seasons and episodes to the document to keep track of episodes they have watched. Through trial and error I found the query I'd use in the mongo shell to update a field in the episodes object but I can't create the right syntax to do this with mongoose inside my route. Can someone look in my tele.js routes at the router.put and tell me what's wrong.

Models (TVSeries.js)

var mongoose = require('mongoose')
var Schema = mongoose.Schema

var tvSchema = new Schema({
  title:String,
  poster:String,
  rated:String,
  program_time:Number,
  network:String,
  airs_on:[],
  streams_on:[],
  genre:[],
  seasons:[
            season_number:Number,
            episodes:[
                       {
                         episode_number:Number,
                         title:String,
                         watched:Boolean
                       }
                     ]
          ]
}, {collection: 'tvShows'});

module.exports = mongoose.model('tv', tvSchema);

Routes (tele.js)

var express = require('express');
var router = express.Router();
var TV = require('../models/TVSeries.js');

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Your Watchlist' });
});

router.get('/api/shows/:id/seasons', function(req, res){
    TV.findById(req.params.id, 'seasons', function(err, data){
        if(err){console.log(err)}
        else{res.json(data)};
    });
});

router.put('/api/shows/:id/seasons/:sid', function(req, res){
    var setField = {
        seasons:[
            {
                season_number:req.params.sid,
                episodes:[
                    req.body
                    ]
            }
        ]
    }

    TV.findOneAndUpdate({"_id":req.params.id}, setField, {upsert:true}, function(err, results){
       if(err){console.log(err)}
       else{
           console.log(setField);
           res.json(results)
       }
    })
})

module.exports = router;

Mongo Shell command

db.tvShows.update({"_id":ObjectId('######################')},{$set: {'seasons.1.episodes.1.title':'This is my title change'}})
Rawle Juglal
  • 395
  • 4
  • 17
  • Still working to find solution. Tried findOneAndUpdate which is closer. I am now able to update the episodes object but instead of just adding another object into the array it is removing all other objects and replacing it with req.body. Does anyone know the solution to this? – Rawle Juglal Oct 30 '16 at 17:14

1 Answers1

1

You can use &elementMatch to find the desire season in the array, and in the setField object you can use the positional $ operator which identify the element matched in the query.

The problem is that if it doesn't find any season that match the season_number, the document will not be updated. In this case you can set another update query to add this season in the seasons array.

router.put('/api/shows/:id/seasons/:sid', function(req, res){

  var query = {
    "_id": req.params.id,
    "seasons": { 
      $elemMatch: { 
        "season_number": req.params.sid 
      } 
    }
  }

  var setField = {
    $addToSet: {
      "seasons.$.episodes": req.body
    }
  }

  TV.findOneAndUpdate(query, setField, {upsert:true}, function(err, results){
    if (err && err.code == 16836) { // no document was matched
      var season = {
        "season_number": req.params.sid
        "episodes": [ req.body]
      }
      TV.findOneAndUpdate({"_id": req.params.id}, {$push: {"seasons": season}} , function(err, result) {
          console.log("Inserted document in array");
          res.json(result)
      }); 
    }
    else if (err){
       console.log(err);
       res.status(500).send('Something wrong!');
    }
    else {
       console.log(setField);
       res.json(results)
    }
  })
})

You can see here some mongodb array operators.

Hope it helps.

lucas.coelho
  • 894
  • 1
  • 9
  • 16
  • I was thinking $push and this is very close to what I was hoping to do. But this is creating this "seasons":[{Season 1 Object Episode 1}, {Season 1 Object Episode 2}]. And I'm hoping I could get it to this "seasons":[{"season_number":1, episodes:[{Episodes Object}, {Episodes Object}]. So from your example I tried creating this so that if :sid was 1 then it would go into season array index 0 episodes array to push req.body like so. var setExpression = 'seasons.'+(req.params.sid-1)+'.episodes'; var setField = {$push: {setExpression:req.body}} But this is returning Error '$push' is empty... – Rawle Juglal Oct 30 '16 at 20:07
  • Thank you this did work. I will have to read up on $elemMatch and $addtoSet as I'm not exactly sure what they are doing. I do have one question about the mongoose .update. Using postman it was hanging when it was adding a new season_number. So I added a res.json at the end of that. But I'm not sure when it will reach the else part...is it when I'm adding an episode object that already matches a season_number that has been created? – Rawle Juglal Oct 31 '16 at 01:24
  • @RawleJuglal I updated the answer. The problem of the hanging request was it wasn't responding on the second update. – lucas.coelho Oct 31 '16 at 12:39
  • Yes, it will reach the else when there was already a season on db – lucas.coelho Oct 31 '16 at 12:42
  • Thanks for all this help in understanding this Lucas.Because the seasons & episodes are embedded it seems like I will have to do all my CRUD operations to episodes inside of the put route, with several else if statements to check if the episode already exists and I need to update or a check on the req.body that's empty signifying I want to delete an episode. This has me questioning whether I should have seasons and episodes be separate collections with their own schema to do crud on. Or maybe removing the array of seasons and make an array of episodes with a season_number field. Any opinion? – Rawle Juglal Nov 01 '16 at 20:32