sql >> Base de Datos >  >> NoSQL >> MongoDB

(mangosta/promesas) ¿Cómo verifica si el documento se creó utilizando findOneAndUpdate con upsert?

En el caso de .findOneAndUpdate() o cualquiera de los .findAndModify() variantes del controlador central para mongoose, la firma de devolución de llamada real tiene "tres" argumentos:

 function(err,result,raw)

El primero es cualquier respuesta de error, luego el documento modificado u original según las opciones y el tercero, que es un resultado de escritura de la declaración emitida.

Ese tercer argumento debería devolver datos como este:

{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e12c65f6044f57c8e09a46 },
  value: { _id: 55e12c65f6044f57c8e09a46, 
           number: 55555555, 
           country: 'US', 
           token: "XXX", 
           appInstalled: true,
           __v: 0 },
  ok: 1 }

Con el campo consistente allí como lastErrorObject.updatedExisting siendo true/false dependiendo del resultado de si se produjo un upsert. Tenga en cuenta que también hay un valor "alterado" que contiene el _id respuesta para el nuevo documento cuando esta propiedad es false , pero no cuando es true .

Como tal, modificaría su manejo para considerar la tercera condición, pero esto solo funciona con una devolución de llamada y no con una promesa:

Inbox.model.findOneAndUpdate(
    { "number": req.phone.number },
    { 
      "$set": {
          "country": req.phone.country,
          "token": hat(),
          "appInstalled": true
      }
    }, 
    { "new": true, "upsert": true },
    function(err,doc,raw) {

      if ( !raw.lastErrorObject.updatedExitsing ) {
         // do things with the new document created
      }
    }
);

Donde también le sugiero que use operadores de actualización en lugar de objetos sin procesar aquí, ya que un objeto sin procesar siempre sobrescribirá todo el documento, sin embargo, operadores como $set solo afecta los campos enumerados.

También tenga en cuenta que cualquier "argumento de consulta" que coincida con la declaración se asigna automáticamente en el nuevo documento siempre que su valor sea una coincidencia exacta que no se encontró.

Dado que el uso de una promesa no parece devolver la información adicional por algún motivo, entonces no veo cómo esto es posible con una promesa que no sea establecer { new: false} y básicamente cuando no se devuelve ningún documento, entonces es uno nuevo.

Tiene todos los datos del documento que se espera que se inserten de todos modos, por lo que no es como si realmente necesita que se devuelvan esos datos de todos modos. De hecho, es cómo los métodos del controlador nativo manejan esto en el núcleo y solo responden con el _id "alterado". valor cuando se produce un upsert.

Esto realmente se reduce a otro tema discutido en este sitio, en:

¿Pueden las promesas tener múltiples argumentos para onFulfilled?

Donde esto realmente se reduce a la resolución de múltiples objetos en una respuesta de promesa, que es algo que no se admite directamente en la especificación nativa, pero hay enfoques enumerados allí.

Entonces, si implementa las promesas de Bluebird y usa .spread() método allí, entonces todo está bien:

var async = require('async'),
    Promise = require('bluebird'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var testSchema = new Schema({
  name: String
});

var Test = mongoose.model('Test',testSchema,'test');
Promise.promisifyAll(Test);
Promise.promisifyAll(Test.prototype);

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      var promise = Test.findOneAndUpdateAsync(
        { "name": "Bill" },
        { "$set": { "name": "Bill" } },
        { "new": true, "upsert": true }
      );

      promise.spread(function(doc,raw) {
        console.log(doc);
        console.log(raw);
        if ( !raw.lastErrorObject.updatedExisting ) {
          console.log( "new document" );
        }
        callback();
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Lo que, por supuesto, devuelve ambos objetos y puede acceder a ellos de manera consistente:

{ _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 }
{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e14b7af6044f57c8e09a4e },
  value: { _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 },
  ok: 1 }

Aquí hay una lista completa que demuestra el comportamiento normal:

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

mongoose.connect('mongodb://localhost/test');

var testSchema = new Schema({
  name: String
});

var Test = mongoose.model('Test',testSchema,'test');

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      Test.findOneAndUpdate(
        { "name": "Bill" },
        { "$set": { "name": "Bill" } },
        { "new": true, "upsert": true }
      ).then(function(doc,raw) {
        console.log(doc);
        console.log(raw);
        if ( !raw.lastErrorObject.updatedExisting ) {
          console.log( "new document" );
        }
        callback();
      });
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Para que conste, el controlador nativo en sí mismo no tiene este problema ya que el objeto de respuesta es, de hecho, el único objeto devuelto aparte de cualquier error:

var async = require('async'),
    mongodb = require('mongodb'),
    MongoClient = mongodb.MongoClient;

MongoClient.connect('mongodb://localhost/test',function(err,db) {

  var collection = db.collection('test');

  collection.findOneAndUpdate(
    { "name": "Bill" },
    { "$set": { "name": "Bill" } },
    { "upsert": true, "returnOriginal": false }
  ).then(function(response) {
    console.log(response);
  });
});

Así que siempre es algo como esto:

{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e13bcbf6044f57c8e09a4b },
  value: { _id: 55e13bcbf6044f57c8e09a4b, name: 'Bill' },
  ok: 1 }