MongoDB – Mongoose query findOneAndUpdate() doesn’t update/duplicates the DB

mongodbnode.jsquery

I am trying to save and update (upsert: true – creates the object if it doesn't exist) the result of the Web Api which contains multiple arrays of data to populate the stock chart. Each time I enter a symbol and click the "Get Quote" button it should fetch data from the web api, save/update it under the Child Schema in the database. For some reason it adds duplicate values. Is there a way to fix avoid duplicate values? Here is the code that I tried….

let curValueSchema = new parentSchemaSymbol()

      curValueSchema.symbol = curValue
      highLow.map(item => {
        curValueSchema.data.push(item)
      })
      const query = { symbol: `${curValue.toUpperCase()}` }
      const update = curValueSchema
      const options = { upsert: true, new: true }
      parentSchemaSymbol.findOneAndUpdate(query, update, options).then(doc => {
        console.log('Saved the symbol', doc)
        return res.send(highLow)
      }).catch(e => {
        console.log(e)
      })

SOLUTION

Because Mongoose by default creates a new MongoDB ObjectId ( this hidden _id field) every time you pass it a Javascript Object to update the field of a document.

To go around you can tell Mongoose to not create a new ObjectId, by making sure your mongoose schema is as followed:

Folder – Models – Stock.js

"_id": false – Has to be added to the Schema

const mongoose = require('mongoose')
mongoose.Promise = global.Promise
mongoose.connect('mongodb://localhost:27017/myapp', { useNewUrlParser: true })
const slug = require('slug')


const childSchemaData = new mongoose.Schema({
  "_id": false,
  date: mongoose.Decimal128,
  open: mongoose.Decimal128,
  high: mongoose.Decimal128,
  low: mongoose.Decimal128,
  close: mongoose.Decimal128,
  volume: mongoose.Decimal128
})

const parentSchemaSymbol = new mongoose.Schema({
  "_id": false,
  symbol: {
    type: String,
    trim: true,
    minlength: 2,
    maxlength: 4,
    required: 'Plese enter a valid symbol, min 2 characters and max 4'
  },
  // Array of subdocuments
  data: [childSchemaData],
  slug: String

});

//we have to PRE-save slug before save the parentSchemaSymbol into DB
parentSchemaSymbol.pre('save', function (next) {
  if (!this.isModified('symbol')) {
    next()//skip it
    return//stop this function from running
  }
  this.slug = slug(this.symbol)
  next()
  //TODO make more resiliant soslug are unique
})

module.exports = mongoose.model('Stock', parentSchemaSymbol)

Best Answer

As MongoDB Github collection documented with respect to Node.js MongoDB Driver API here Find a document and update it in one atomic operation, requires a write lock for the duration of the operation.

Syntax

findOneAndUpdate(filter, update, options, callback)

// Example of a simple findOneAndUpdate operation

var MongoClient = require('mongodb').MongoClient,
  test = require('assert');
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
  // Get the collection
  var col = db.collection('find_one_and_update');
  col.insertMany([{a:1, b:1}], {w:1}, function(err, r) {
    test.equal(null, err);
    test.equal(1, r.result.n);

    col.findOneAndUpdate({a:1}
      , {$set: {d:1}}
      , {
            projection: {b:1, d:1}
          , sort: {a:1}
          , returnOriginal: false
          , upsert: true
        }
      , function(err, r) {
        test.equal(null, err);
        test.equal(1, r.lastErrorObject.n);
        test.equal(1, r.value.b);
        test.equal(1, r.value.d);

        db.close();
    });
  });
});

// Example of a simple findOneAndUpdate operation using a Promise.

var MongoClient = require('mongodb').MongoClient,
  test = require('assert');
MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
  // Get the collection
  var col = db.collection('find_one_and_update_with_promise');
  col.insertMany([{a:1, b:1}], {w:1}).then(function(r) {
    test.equal(1, r.result.n);

    col.findOneAndUpdate({a:1}
      , {$set: {d:1}}
      , {
            projection: {b:1, d:1}
          , sort: {a:1}
          , returnOriginal: false
          , upsert: true
        }
      ).then(function(r) {
        test.equal(1, r.lastErrorObject.n);
        test.equal(1, r.value.b);
        test.equal(1, r.value.d);

        db.close();
    });
  });
});

// Example of a simple findOneAndUpdate operation using a Generator and the co module.

var MongoClient = require('mongodb').MongoClient,
  co = require('co');
  test = require('assert');

co(function*() {
  var db = yield MongoClient.connect('mongodb://localhost:27017/test');
  // Get the collection
  var col = db.collection('find_one_and_update_with_generators');
  var r = yield col.insertMany([{a:1, b:1}], {w:1});
  test.equal(1, r.result.n);

  var r = yield col.findOneAndUpdate({a:1}
    , {$set: {d:1}}
    , {
          projection: {b:1, d:1}
        , sort: {a:1}
        , returnOriginal: false
        , upsert: true
      }
    );
  test.equal(1, r.lastErrorObject.n);
  test.equal(1, r.value.b);
  test.equal(1, r.value.d);

  db.close();
});

As Mongoosejs documented here

Query.prototype.findOneAndUpdate() Parameters

  • [query] «Object|Query»
  • [doc] «Object»

[options] «Object»

  • [options.rawResult] «Boolean» if true, returns the raw result from the MongoDB driver
  • [options.strict] «Boolean|String» overwrites the schema's strict mode option
  • [options.multipleCastError] «Boolean» by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
  • [options.lean] «Object» if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See Query.lean().
  • [callback] «Function» optional params are (error, doc), unless rawResult is used, in which case params are (error, writeOpResult)

This function triggers the following middleware.

findOneAndUpdate()

Available options

  • new: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)

  • upsert: bool - creates the object if it doesn't exist. defaults to false.

  • fields: {Object|String} - Field selection. Equivalent to .select(fields).findOneAndUpdate()

  • sort: if multiple docs are found by the conditions, sets the sort order to choose which doc to update

  • maxTimeMS: puts a time limit on the query - requires mongodb >= 2.6.0

  • runValidators: if true, runs update validators on this command. Update validators validate the update operation against the model's schema.

  • setDefaultsOnInsert: if this and upsert are true, mongoose will apply the defaults specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on MongoDB's $setOnInsert operator.

  • rawResult: if true, returns the raw result from the MongoDB driver

  • context (string) if set to 'query' and runValidators is on, this will refer to the query in custom validator functions that update validation runs. Does nothing if runValidators is false.

Callback Signature function(error, doc) { // error: any errors that occurred // doc: the document before updates are applied if new: false, or after updates if new = true }

Examples

query.findOneAndUpdate(conditions, update, options, callback) // executes
query.findOneAndUpdate(conditions, update, options)  // returns Query
query.findOneAndUpdate(conditions, update, callback) // executes
query.findOneAndUpdate(conditions, update)           // returns Query
query.findOneAndUpdate(update, callback)             // returns Query
query.findOneAndUpdate(update)                       // returns Query
query.findOneAndUpdate(callback)                     // executes
query.findOneAndUpdate()                             // returns Query