Mongoose overwrites the document and the `$ set` fields

Let's 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 as follows:

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

      

A property that is not specified should be removed.

How should I do it?

+4


source to share


2 answers


Actually, but because mongoose actually "fiddles" with updating undercover, this is actually the default action to send to a regular MongoDB function.

So mongoose considers "wise" as a convenient way to "assume" that you intended to issue an instruction here . Since you don't really want to do this in this case, you disable this behavior by using in parameters passed to anyone : $set

{ overwrite: true }

.update()

As a complete 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"
}

      

The document display is "overwritten" because we have eliminated an operation that would otherwise be interpolated. The two examples are shown first without the option that applies the modifier , and then the "with" option , where the object you passed to "update" is respected and no such modifier is applied. $set

overwrite

$set

overwrite

$set

Note that this is how the MongoDB node driver does this by default. Thus, the behavior of adding to "implicit" is done by mongoose unless you tell it not to. $set


NOTE - In fact, the true way to "replace" is to use replaceOne

either as an API method and using . is a legacy of how mongoose wants to apply as described and demonstrated above, however the official MongoDB API presents it as a "special" operation king that does not allow atomic operators such as within an expression and will fail if you try. replaceOne()

bulkWrite()

overwrite

$set

replaceOne

update()

$set

This is much clearer in terms of semantics, as the replacement shows very clearly what the method is actually used for. In standard API calls to, update()

variants, of course, still allow you to omit atomic operators and simply replace the content anyway. But warnings are to be expected.

+7


source


You can pass a parameter upsert

and it will replace the 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 the doc

document was found in the callback, but not updated. Hence, you need to do 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);
    });
  }
);

      

+2


source







All Articles