Mongoose overwrite the document rather that `$set`

2019-01-12 10:47发布

问题:

Say, i have a document:

{
  _id: 'some_mongodb_id',
  name: 'john doe',
  phone: '+12345678901',
}

I want to update this document:

.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})

And the result should be this:

{
  _id: 'some_mongodb_id',
  name: 'Dan smith',
}

The property, that is not specified, should be removed.

How do i do that?

回答1:

Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.

So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:

As a full example:

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

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const testSchema = new Schema({
  name: String,
  phone: String
});

const Test = mongoose.model('Test', testSchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    // Clean data
    await Promise.all(
      Object.keys(conn.models).map( m => conn.models[m].remove({}) )
    );

    // Create a document
    let test = await Test.create({
      name: 'john doe',
      phone: '+12345678901'
    });
    log(test);

    // This update will apply using $set for the name
    let notover = await Test.findOneAndUpdate(
      { _id: test._id },
      { name: 'Bill S. Preston' },
      { new: true }
    );
    log(notover);

    // This update will just use the supplied object, and overwrite
    let updated = await Test.findOneAndUpdate(
      { _id: test._id },
      { name: 'Dan Smith' },
      { new: true, overwrite: true }
    );
    log(updated);


  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()

Produces:

Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
  "__v": 0,
  "name": "john doe",
  "phone": "+12345678901",
  "_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
  "_id": "596efb0ec941ff0ec319ac1e",
  "name": "Bill S. Preston",
  "phone": "+12345678901",
  "__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
  "_id": "596efb0ec941ff0ec319ac1e",
  "name": "Dan Smith"
}

Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.

Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.



回答2:

You can pass upsert option, and it will replace document:

var collection = db.collection('test');
collection.findOneAndUpdate(
  {'_id': 'some_mongodb_id'},
  {name: 'Dan smith Only'},
  {upsert: true},
  function (err, doc) {
    console.log(doc);
  }
);

But the problem here - is that doc in callback is found document but not updated. Hence you need perform something like this:

var collection = db.collection('test');
collection.update(
  {'_id': 'some_mongodb_id'},
  {name: 'Dan smith Only'},
  {upsert: true},
  function (err, doc) {
    collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
        console.log(doc);
    });
  }
);