Update a subdocument in a parent document

Hey guys, im trying to update a single field in a subdocument of a parentdocument using Mongoose. I searched and searched and tried different things but nothing worked, i even tried to filter all the id’s i got back and look if that is in the params but i dont think thats a good way of doing it…

How can i achieve this? This is my code:

  editSubscription(req, res) {
        const token = req.headers.authorization;
        jwt.verify(token, req.app.get('yourSecretKey'), function (err, payload) {
            userModel.updateOne({ _id: payload.user._id, "subscriptions._id": req.params.id }, { "$set": { "subscriptions.$": req.body } }).then(user => {
               console.log(user)
            }).catch(err => {
               console.log(err)
           })
        })
    }

the output of the console.log = { n: 0, nModified: 0, ok: 1 }
I hope someone can help me out…

Still learning Mongoose; but maybe you are looking for findOneAndUpdate? It includes a callback with the updated entry. Also if the _id is unique, just findByIdAndUpdate should work.

It’s hard to tell what you are looking to find/update without a basic schema or object of the target.

I have 2 Schema’s a user and a subscription schema and they are connected, when a user adds a subscription it will be added in the User Schema in a array.

User Model:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const saltRounds = 10;
const Role = require('../config/roles')
const subscription = require('./subscriptionModel')

const Schema = mongoose.Schema;

const userModel = new Schema({
    created: { type: Date, default: Date.now },
    updated: { type: Date, default: Date.now},
    fullname: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        unique: true
    },
    birthDate: {
        type: String,
        required: true
    },
    password: {
        type: String,
        required: true
    },
    profileImage: {
        type: String
    },
    role: {
        type: String,
        default: Role.user
    },
    subscriptions: [subscription.modelName]
})

// hash user password before saving into database
userModel.pre('save', async function save(next) {
    if (!this.isModified('password')) return next();
    try {
      const salt = await bcrypt.genSalt(saltRounds);
      this.password = await bcrypt.hash(this.password, salt);
      return next();
    } catch (err) {
      return next(err);
    }
});
  
// Compare passwords when user tries to log in.
userModel.methods.comparePassword = function(candidatePassword, cb) {
    bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
        if (err) return cb(err);
        cb(null, isMatch);
    });
};

module.exports = mongoose.model('User', userModel)

And Subscription Model:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const subscriptionModel = Schema({
    name: {
        type: String,
        required: true,
    },
    price: {
        type: Number,
        required: true
    },
    paymentDate: {
        type: String,
        required: true
    },
    active: {
        type: Boolean,
        default: true
    },
    created: { type: Date, default: Date.now },
    updated: { type: Date, default: Date.now },
    users: { type: Schema.Types.ObjectId, ref: 'User' },
}, {
    collection: "Subscriptions"
  })

module.exports = mongoose.model('Subscriptions', subscriptionModel)

Then i would do something like

 userModel.updateOne({
                _id: payload.user._id
            }, {
                $set: {
                    'subscriptions.0.price': req.body.price
                }
            }, function (err, result) {
                console.log(result)
            })

this works but it updates only the first item in an array…

@pjonp i tried findOneAndUpdate but im getting an error, i searched and i know i need to use updateOne …

I’m still searching …

Here is how I would approach this:

userModel.findById(payload.user._id, i => {
//Main Target
let subs = i.subscriptions
subs.find( { name: 'youtube' }, j => {  //assuming known (unique) target  (not sure if possible with mongoose; if not search Object key/values)
//Sub Target 
j.price = req.body.price
j.save( err => { } )
})
// i.save(cb) if using Object
})

Again, I’m still new with mongoose and this is how I would approach it. I hope you get an answer and others can help. Good luck!

1 Like

Sadly this doesn’t work either but thanks for your input!

Ok last attempt :sweat_smile:


userModel.findById(payload.user._id, i => {
//Main Target
let subID = i.subscriptions['_id']
subscriptionModel.findById( subID, j => {
// now you have the 'subscriptionModel' child (hopefully)
})
})