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 }