Jak zauważono we wcześniejszym komentarzu, masz dwa podstawowe podejścia do ustalenia, czy coś zostało „stworzone”, czy nie. Są to:
-
Zwróć
rawResult
w odpowiedzi i sprawdźupdatedExisting
właściwość, która mówi Ci, czy jest to „przesadne”, czy nie -
Ustaw
new: false
tak, że "brak dokumentu" jest faktycznie zwracany w wyniku, gdy w rzeczywistości jest to "upsert"
Jako wykaz do zademonstrowania:
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()
}
})()
A wynik:
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"
}
Tak więc pierwszy przypadek dotyczy tego kodu:
User.findOneAndUpdate(
{ username: 'Bill' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: true, rawResult: true }
)
Większość opcji jest tutaj standardowa jako „wszystkie” "upsert"
akcje spowodują, że zawartość pola zostanie użyta do „dopasowania” (tj. username
) to „zawsze” utworzone w nowym dokumencie, więc nie musisz $set
to pole. Aby faktycznie nie „modyfikować” innych pól w kolejnych żądaniach, możesz użyć $setOnInsert
, który dodaje te właściwości tylko podczas "upsert"
akcja, w której nie znaleziono dopasowania.
Tutaj standardowe new: true
służy do zwrócenia "zmodyfikowanego" dokumentu z akcji, ale różnica polega na rawResult
jak pokazano w zwróconej odpowiedzi:
{
"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
}
}
}
Zamiast „dokumentu mangusty” otrzymujesz rzeczywistą „surową” odpowiedź od kierowcy. Rzeczywista treść dokumentu znajduje się pod "value"
właściwość, ale jest to "lastErrorObject"
jesteśmy zainteresowani.
Tutaj widzimy właściwość updatedExisting: false
. Oznacza to, że faktycznie znaleziono „brak dopasowania”, a zatem „utworzono” nowy dokument. Możesz więc użyć tego do ustalenia, że tworzenie rzeczywiście miało miejsce.
Po ponownym wprowadzeniu tych samych opcji zapytania wynik będzie inny:
{
"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
wartość jest teraz true
, a to dlatego, że istniał już dokument pasujący do username: 'Bill'
w zapytaniu. Oznacza to, że dokument już tam był, więc możesz rozgałęzić swoją logikę, aby zwrócić "Błąd" lub dowolną odpowiedź.
W innym przypadku może być pożądane, aby „nie” zwracać „surowej” odpowiedzi i zamiast tego użyć zwróconego „dokumentu mangusty”. W tym przypadku zmieniamy wartość na new: false
bez rawResult
opcja.
User.findOneAndUpdate(
{ username: 'Ted' },
{ $setOnInsert: { password: 'password' } },
{ upsert: true, new: false }
)
Ma zastosowanie większość tych samych rzeczy, z wyjątkiem tego, że teraz akcja jest oryginalna zwracany jest stan dokumentu w przeciwieństwie do „zmodyfikowanego” stanu dokumentu „po” akcji. Dlatego gdy nie ma dokumentu, który faktycznie pasuje do instrukcji "zapytanie", zwracany wynik to null
:
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null // <-- Got null in response :(
To mówi, że dokument został "utworzony" i można argumentować, że już wiesz, jaka powinna być zawartość dokumentu, ponieważ wysłałeś te dane za pomocą instrukcji (najlepiej w $setOnInsert
). Chodzi o to, że już wiesz, co zwrócić „jeśli” potrzebujesz, aby faktycznie zwrócić zawartość dokumentu.
Z kolei „znaleziony” dokument zwraca „pierwotny stan” dokumentu „przed” jego modyfikacją:
{
"_id": "5adfc8696878cfc4992e7639",
"username": "Ted",
"__v": 0,
"password": "password"
}
Dlatego każda odpowiedź, która nie jest null
” jest zatem wskazówką, że dokument był już obecny i ponownie możesz rozgałęzić swoją logikę w zależności od tego, co faktycznie otrzymano w odpowiedzi.
Więc to są dwa podstawowe podejścia do tego, o co prosisz, i na pewno „działają”! I tak jak jest to pokazane i powtarzalne za pomocą tych samych stwierdzeń tutaj.
Uzupełnienie – Rezerwuj zduplikowany klucz w przypadku złych haseł
Jest jeszcze jedno ważne podejście, o którym wspomniano w pełnej liście, a mianowicie po prostu .insert()
( lub .create()
z modeli mongoose ) nowe dane i zgłaszany jest błąd „duplikatu klucza”, w którym faktycznie napotkano „unikalną” właściwość indeksu. Jest to prawidłowe podejście, ale istnieje jeden konkretny przypadek użycia w „walidacji użytkowników”, który jest przydatnym elementem obsługi logiki, a jest nim „walidacja haseł”.
Jest to więc dość powszechny wzorzec pobierania informacji o użytkowniku przez username
i password
połączenie. W przypadku „upsert” ta kombinacja uzasadnia jako „unikalna” i dlatego próba „insert” jest podejmowana, jeśli nie zostanie znalezione dopasowanie. Właśnie to sprawia, że dopasowanie hasła jest tutaj przydatną implementacją.
Rozważ następujące kwestie:
// 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 }
);
Przy pierwszej próbie tak naprawdę nie mamy username
dla "Fred"
, więc nastąpi "upsert" i wszystkie inne rzeczy, jak już opisano powyżej, będą identyfikować, czy był to twór, czy znaleziony dokument.
Poniższa instrukcja używa tej samej username
wartość, ale zapewnia inne hasło niż to, co jest rejestrowane. Tutaj MongoDB próbuje "utworzyć" nowy dokument, ponieważ nie pasuje on do kombinacji, ale ponieważ username
oczekuje się, że będzie "unique"
otrzymujesz komunikat „Błąd zduplikowanego klucza”:
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
Więc powinieneś zdać sobie sprawę, że teraz masz trzy warunki do oceny „za darmo”. Istota:
- Zmiana „upsert” została zarejestrowana przez
updatedExisting: false
lubnull
wynik w zależności od metody. - Wiesz, że dokument ( przez kombinację ) „istnieje” za pomocą
updatedExisting: true
lub gdzie zwracany dokument był „nienull
". - Jeśli
password
podane nie było zgodne z tym, co już istniało dlausername
, otrzymasz „błąd zduplikowanego klucza”, który możesz przechwycić i odpowiednio zareagować, informując użytkownika, że „hasło jest nieprawidłowe”.
Wszystko to od jednego żądanie.
To jest główny powód używania „upserts” w przeciwieństwie do zwykłego wrzucania wstawek do kolekcji, ponieważ możesz uzyskać różne rozgałęzienia logiki bez wysyłania dodatkowych żądań do bazy danych w celu określenia, „który” z tych warunków powinien być rzeczywistą odpowiedzią.