Как отмечалось в комментарии ранее, у вас есть два основных подхода, чтобы выяснить, было ли что-то «создано» или нет. Это либо:
Верните rawResult
в ответе и проверьте свойство updatedExisting
, которое сообщает вам, является ли это "upsert" или нет
Установите new: false
так, чтобы "no document" фактически возвращался в результате, когда это фактически "upsert"
В качестве списка для демонстрации:
const { Schema } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/thereornot';
mongoose.set('debug', true);
mongoose.Promise = global.Promise;
const userSchema = new Schema({
username: { type: String, unique: true }, // Just to prove a point really
password: String
});
const User = mongoose.model('User', userSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Shows updatedExisting as false - Therefore "created"
let bill1 = await User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
);
log(bill1);
// Shows updatedExisting as true - Therefore "existing"
let bill2 = await User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
);
log(bill2);
// Test with something like:
// if ( bill2.lastErrorObject.updatedExisting ) throw new Error("already there");
// Return will be null on "created"
let ted1 = await User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
);
log(ted1);
// Return will be an object where "existing" and found
let ted2 = await User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
);
log(ted2);
// Test with something like:
// if (ted2 !== null) throw new Error("already there");
// Demonstrating "why" we reserve the "Duplicate" error
let fred1 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'password' },
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
log(fred1); // null - so okay
let fred2 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
А на выходе:
Mongoose: users.remove({}, {})
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
"lastErrorObject": {
"n": 1,
"updatedExisting": false,
"upserted": "5adfc8696878cfc4992e7634"
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
"lastErrorObject": {
"n": 1,
"updatedExisting": true
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Таким образом, первый случай фактически рассматривает этот код:
User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
)
Большинство опций здесь стандартно, так как «все» "upsert"
действия приведут к тому, что содержимое поля, используемое для «соответствия» (т. Е. username
), будет «всегда» создан в новом документе, поэтому вам не нужно $set
это поле. Чтобы не «модифицировать» другие поля в последующих запросах, вы можете использовать $setOnInsert
, который добавляет эти свойства только во время действия "upsert"
, когда совпадение не найдено.
Здесь стандарт new: true
используется для возврата «измененного» документа из действия, но разница в rawResult
, как показано в возвращенном ответе:
{
"lastErrorObject": {
"n": 1,
"updatedExisting": false,
"upserted": "5adfc8696878cfc4992e7634"
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Вместо «мангуста-документа» вы получаете реальный «сырой» ответ от водителя. Фактическое содержимое документа находится в свойстве "value"
, но это интересующий нас "lastErrorObject"
.
Здесь мы видим свойство updatedExisting: false
. Это указывает на то, что «совпадение не найдено», поэтому новый документ «создан». Таким образом, вы можете использовать это, чтобы определить, что творение действительно произошло.
Когда вы снова введете те же параметры запроса, результат будет другим:
{
"lastErrorObject": {
"n": 1,
"updatedExisting": true // <--- Now I'm true
},
"value": {
"_id": "5adfc8696878cfc4992e7634",
"username": "Bill",
"__v": 0,
"password": "password"
},
"ok": 1,
"operationTime": "6548172736517111811",
"$clusterTime": {
"clusterTime": "6548172736517111811",
"signature": {
"hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId": 0
}
}
}
Значение updatedExisting
теперь равно true
, и это потому, что уже был документ, который соответствует username: 'Bill'
в операторе запроса. Это говорит о том, что документ уже был там, так что вы можете затем развернуть свою логику, чтобы вернуть «Ошибка» или любой другой ответ, который вы хотите.
В другом случае может быть желательно не возвращать «сырой» ответ и использовать вместо него возвращенный «документ Мангуста». В этом случае мы изменяем значение на new: false
без опции rawResult
.
User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
)
В большинстве случаев применимо то же самое, за исключением того, что теперь действие представляет собой оригинальное состояние документа, возвращаемое в противоположность «измененному» состоянию документа «после» действия. Поэтому, когда нет документа, который действительно соответствует выражению «запрос», возвращаемый результат будет null
:
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null // <-- Got null in response :(
Это говорит о том, что документ был "создан", и можно утверждать, что вы уже знаете, каким должно быть содержимое документа, поскольку вы отправили эти данные с заявлением (в идеале в $setOnInsert
). В сущности, вы уже знаете, что для возврата «если» вам необходимо вернуть содержимое документа.
Напротив, «найденный» документ возвращает «исходное состояние», показывающее документ «до того», как он был изменен:
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Поэтому любой ответ, который "не null
", следовательно, является указанием на то, что документ уже присутствовал, и снова вы можете использовать свою логику в зависимости от того, что было фактически получено в ответе.
Так что это два основных подхода к тому, что вы просите, и они, безусловно, «работают»! И так же, как это продемонстрировано и воспроизводимо с теми же утверждениями здесь.
Приложение - Резервный Дубликат Ключа для плохих паролей
Существует еще один действительный подход, на который намекают и в полном листинге, который заключается в простом .insert()
(или .create()
в моделях mongoose) новых данных и выдаче ошибки «дублирующий ключ», когда «уникальное» свойство по индексу фактически встречается. Это правильный подход, но в «валидации пользователя» есть один конкретный вариант использования, который представляет собой удобную часть логической обработки, а именно «проверка паролей».
Так что это довольно распространенный способ получения информации о пользователях с помощью комбинации username
и password
.В случае «upsert» эта комбинация оправдывается как «уникальная», и поэтому предпринимается попытка «вставить», если совпадение не найдено.Это именно то, что делает сопоставление пароля полезной реализацией.
Рассмотрим следующее:
// Demonstrating "why" we reserve the "Duplicate" error
let fred1 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'password' },
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
log(fred1); // null - so okay
let fred2 = await User.findOneAndUpdate(
{ username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
{ $setOnInsert: { } },
{ upsert: true, new: false }
);
С первой попытки у нас фактически нет username
для "Fred"
так что произойдет «upsert», и все другие вещи, как уже было описано выше, будут определять, было ли это создание или найденный документ.
В приведенном ниже утверждении используется то же значение username
, но он предоставляетпароль отличается от того, что записано.Здесь MongoDB пытается «создать» новый документ, поскольку он не совпадает по комбинации, а поскольку ожидается, что username
будет "unique"
, вы получите «Ошибка повторяющегося ключа»:
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
Итак, вы должны понимать, что теперь у вас есть три условия для оценки "бесплатно".Быть:
- "upsert" был записан либо результатом
updatedExisting: false
, либо null
в зависимости от метода. - Вы знаете, что документ (по комбинации) "существует"либо через
updatedExisting: true
, либо там, где возвращаемый документ был "not null
". - Если предоставленный
password
не соответствует тому, что уже существовало для username
, то вы получите«ошибка дублированного ключа», которую вы можете перехватить и отреагировать соответствующим образом, сообщая пользователю в ответ, что «неверный пароль».
Все это из одного запроса.
Это основная причина использования «upserts», а не просто броска вставок в коллекцию, поскольку вы можете получить различное ветвление логики без дополнительных запросов к базе данных, чтобы определить, «какое» из этих условий должно бытьфактический ответ.