MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

(mangusto/promessas) Como você verifica se o documento foi criado usando findOneAndUpdate com upsert


No caso de .findOneAndUpdate() ou qualquer um dos .findAndModify() variantes do driver principal para mangusto, a assinatura de retorno de chamada real tem "três" argumentos:
função
 function(err,result,raw)

Sendo o primeiro qualquer resposta de erro, depois o documento modificado ou original dependendo das opções e o terceiro que é um resultado de gravação da declaração emitida.

Esse terceiro argumento deve retornar dados assim:
{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e12c65f6044f57c8e09a46 },
  value: { _id: 55e12c65f6044f57c8e09a46, 
           number: 55555555, 
           country: 'US', 
           token: "XXX", 
           appInstalled: true,
           __v: 0 },
  ok: 1 }

Com o campo consistente como lastErrorObject.updatedExisting sendo true/false dependendo do resultado se um upsert ocorreu. Observe que também há um valor "upserted" contendo o _id resposta para o novo documento quando esta propriedade for false , mas não quando é true .

Dessa forma, você modificaria seu tratamento para considerar a terceira condição, mas isso só funciona com um retorno de chamada e não com uma promessa:
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
      }
    }
);

Onde eu também sugiro fortemente que você use operadores de atualização em vez de objetos brutos aqui, pois um objeto bruto sempre substituirá todo o documento, mas operadores como $set apenas afetam os campos listados.

Observando também que quaisquer "argumentos de consulta" correspondentes à instrução são atribuídos automaticamente no novo documento, desde que seu valor seja uma correspondência exata que não foi encontrada.

Dado que o uso de uma promessa não parece retornar as informações adicionais por algum motivo, não vejo como isso é possível com uma promessa além da configuração de { new: false} e basicamente quando nenhum documento é retornado, então é um novo.

Você tem todos os dados do documento que devem ser inseridos de qualquer maneira, portanto, não é como se você realmente precisasse desses dados retornados de qualquer maneira. Na verdade, é como os métodos do driver nativo lidam com isso no núcleo e respondem apenas com o _id "upserted" valor quando ocorre um upsert.

Isso realmente se resume a outra questão discutida neste site, em:

As promessas podem ter vários argumentos para onFulfilled?

Onde isso realmente se resume à resolução de vários objetos em uma resposta de promessa, que é algo não suportado diretamente na especificação nativa, mas existem abordagens listadas lá.

Então, se você implementar as promessas do Bluebird e usar o .spread() método lá, então está tudo bem:
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();
  }
);

O que, obviamente, retorna os dois objetos e você pode acessá-los de forma consistente:
{ _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 }
{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e14b7af6044f57c8e09a4e },
  value: { _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 },
  ok: 1 }

Aqui está uma lista completa demonstrando o comportamento 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 registro, o próprio driver nativo não tem esse problema, pois o objeto de resposta é, na verdade, o único objeto retornado além de qualquer erro:
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);
  });
});

Então é sempre algo assim:
{ lastErrorObject:
   { updatedExisting: false,
     n: 1,
     upserted: 55e13bcbf6044f57c8e09a4b },
  value: { _id: 55e13bcbf6044f57c8e09a4b, name: 'Bill' },
  ok: 1 }