node.jsmongodbmongoosesubdocument

Using pull in mongoose model


Should this work? I am trying to remove a single subdocument (following) from a document (this) in the UserSchema model.

UserSchema.methods.unFollow = function( id ) {
var user = this

return Q.Promise( function ( resolve, reject, notify ) {
    var unFollow = user.following.pull( { 'user': id } )

    console.log( unFollow )

    user.save( function ( error, result ) {
        resolve( result )   
    })  
})
}

These are the schemas:

var Follows = new mongoose.Schema({
    user: String,
    added: Number
})

var UserSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        unique: true
    },
    following: [ Follows ]
})

user-controller.js

/*
Unfollow user.
*/
exports.unFollow = function ( req, res ) {

    User.findOne( { token: req.token }, function ( error, user ) {
        user.unfollow( req.body.id )
        .onResolve( function ( err, result ) {
            if ( err || !result ) return res.status( 500 ).json( "User could not be unfollowed." )

            return res.status( 200 ).json( "User unfollowed." )
        })
    })
}

user-model.js

/*
Unfollow a user.
*/
UserSchema.method( 'unfollow', function unfollow ( id ) {
    this.following.pull( { user: id } )

    return this.save()
})

Solution

  • You generally assign methods using the method function:

    UserSchema.method('unFollow', function unFollow(id) {
      var user = this;
    
      user.following.pull({_id: id});
      // Returns a promise in Mongoose 4.X
      return user.save();
    });
    

    Also, as noted, you don't need to use Q as save will return a mongoose promise.

    UPDATE: Mongoose's array pull method will work with matching primitive values but with subdocument objects it will only match on _id.

    UPDATE #2: I just noticed your updated question shows that your controller is doing a lookup first, modifying the returned document and then saving the document back to the server. Why not create a static rather than a method to do what you want? This has the added bonus of being a single call to the DB rather than two per operation.

    Example:

    UserSchema.static('unfollow', function unfollow(token, id, cb) {
      var User = this;
    
      // Returns a promise in Mongoose 4.X
      // or call cb if provided
      return User.findOneAndUpdate({token: token}, {$pull: {follows: {user: id}}}, {new: true}).exec(cb);
    });
    
    User.unfollow(req.token, req.body.id).onResolve(function (err, result) {
      if (err || !result) { return res.status(500).json({msg: 'User could not be unfollowed.'}); }
    
      return res.status(200).json({msg: 'User unfollowed.'})
    });
    

    Bonus follow static:

    UserSchema.static('follow', function follow(token, id, cb) {
      var User = this;
    
      // Returns a promise in Mongoose 4.X
      // or call cb if provided
      return User.findOneAndUpdate({token: token}, {$push: {follows: {user: id}}}, {new: true}).exec(cb);
    });
    
    User.follow(req.token, req.body.id).onResolve(function (err, result) {
      if (err || !result) { return res.status(500).json({msg: 'User could not be followed.'}); }
    
      return res.status(200).json({msg: 'User followed.'})
    });