0

I have an instance method in Mongoose where I need to perform Mongo's $inc operator on a field. My understanding is that since v3, Mongoose does not support $inc in any form other than an update method (e.g. there's no Mongoose method to take a Mongoose document object's field and perform $inc on it, then call save in a way that would avoid race conditions)

The callback function needs an updated user object with the update changes reflected. However, updating in this way does not update the original document from which we are performing the instance method. It's balance and purchases fields remain untouched.

usersSchema.methods.completePurchase = function(item, incBalance, cb) {
  return this.update({
    $addToSet: {
      "purchases": item
    },
    $inc: {
      "balance": incBalance
    }
  }, function(err) {
    // I NEED THE UPDATED USER AT THIS POINT
    return cb(err);
  });
};

As an alternate, I've tried to call findByIdAndUpdate on the model itself, which returns the updated document. Then I leave it up to the calling method to do something useful with the returned updated user object.

usersSchema.methods.completePurchase = function(item, incBalance, cb) {
  var model;
  model = this.model(this.constructor.modelName);
  return model.findByIdAndUpdate(this._id, {
    $addToSet: {
      "purchases": item
    },
    $inc: {
      "balance": incBalance
    }
  }, function(err, updatedUser) {
    // CRASHES BEFORE REACHING THIS POINT
    return cb(err, updatedUser);
  });
};

But doing this results in the following error

project/node_modules/mongoose/node_modules/mquery/lib/utils.js:26
if (/ObjectI[dD]$/.test(obj.constructor.name)) {
                   ^
RangeError: Maximum call stack size exceeded

What is the proper way to perform an $inc or $addToSet call within a Mongoose instance method that will make the updated document accessible?

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
Ted Avery
  • 5,639
  • 3
  • 30
  • 29

1 Answers1

1

This would seem like the droids you are looking for:

usersSchema.methods.completePurchase = (item, incBalance, cb) ->
  model = @model(@constructor.modelName, @schema)
  model.findByIdAndUpdate @_id,
    $addToSet:
      purchases: item

    $inc:
      balance: incBalance
  , cb

Or in JavaScript:

usersSchema.methods.completePurchase = function(item, incBalance, cb) {

  var model = this.model(this.constructor.modelName, this.schema);
  return model.findByIdAndUpdate(this._id, {
    $addToSet: {
      purchases: item
    },
    $inc: {
      balance: incBalance
    }
  },
  cb);
};

And you would call as (JavaScript, sorry!):

user.completePurchase(item, incBalance function(err,doc) {
    // Something here
})

Or at least that works for me.


MyTest


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

mongoose.connect('mongodb://localhost/nodetest')

var childSchema = new Schema({ name: 'string' });

var parentSchema = new Schema({
  children: [childSchema]
});

parentSchema.methods.findMe = function(cb) {
  return this.model(this.constructor.modelName, this.schema)
    .findById(this._id, cb);
};

var Parent = mongoose.model('Parent', parentSchema);

var parent = new Parent( { children: [{ name: 'Bart' }] });

parent.save(function(err,doc) {

  console.log( "saved: " + doc );
  doc.findMe(function(err,doc2) {
    console.log( "found: " + doc2 );
  });

});
Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • Thanks Neil, but I'm confused. Isn't your solution logically the same as my second one? (I was mid-edit when you answered converting coffee to JS so maybe you didn't see it). The only difference I see is that you construct the model var with a schema as well. I gave that a shot and I'm still getting the crash I mentioned. – Ted Avery Mar 19 '14 at 03:01
  • @TedAvery There is the `Schema` argument included and also this doesn't nest callbacks. I just tested with a .`findById()` and that was fine. – Neil Lunn Mar 19 '14 at 03:03
  • @TedAvery Included the full test code that I ran. Might help you spot the difference. – Neil Lunn Mar 19 '14 at 03:22