前のコメントで述べたように、何かが「作成された」かどうかを判断するための2つの基本的なアプローチがあります。これらは次のいずれかです:
-
rawResult
を返します 応答でupdatedExisting
を確認してください 「アップサート」かどうかを示すプロパティ -
new: false
を設定します そのため、実際には「アップサート」であるのに、結果として「ドキュメントなし」が実際に返されます。
デモンストレーション用のリストとして:
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 }
)
アクションがオリジナルであることを除いて、ほとんど同じことが当てはまります。 アクションの「後」のドキュメントの「変更された」状態とは対照的に、ドキュメントの状態が返されます。したがって、「query」ステートメントに実際に一致するドキュメントがない場合、返される結果は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
ではない応答 「したがって、これはドキュメントがすでに存在していることを示しており、応答で実際に受信した内容に応じてロジックを分岐させることができます。
つまり、これらはあなたが求めているものに対する2つの基本的なアプローチであり、間違いなく「機能する」のです。そして、ここで同じステートメントで示され、再現可能であるのと同じように。
補遺-不正なパスワード用に重複キーを予約
完全なリストでも示唆されているもう1つの有効なアプローチがあります。それは、本質的には単に.insert()
です。 (または.create()
マングースモデルから)新しいデータがあり、インデックスによる「一意の」プロパティが実際に検出された場合に「重複キー」エラーがスローされます。これは有効なアプローチですが、「ユーザー検証」には便利なロジック処理の1つの特定のユースケースがあり、それは「パスワードの検証」です。
したがって、username
でユーザー情報を取得するのはかなり一般的なパターンです。 およびpassword
組み合わせ。 「アップサート」の場合、この組み合わせは「一意」として正当化されるため、一致するものが見つからない場合は「挿入」が試行されます。これこそが、パスワードの照合をここでの便利な実装にしている理由です。
次のことを考慮してください:
// 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"
の場合 、したがって、「アップサート」が発生し、上記ですでに説明した他のすべてのことが発生して、それが作成されたドキュメントであるか、見つかったドキュメントであるかが識別されます。
次のステートメントは、同じusername
を使用します 値ですが、記録されたものとは異なるパスワードを提供します。ここで、MongoDBは、組み合わせで一致しなかったため、ただしusername
のために、新しいドキュメントを「作成」しようとします。 "unique"
であることが期待されます 「キーの重複エラー」が発生します:
{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }
ですから、あなたが気付くべきことは、あなたが今3つを手に入れるということです 「無料」を評価するための条件。存在する:
- 「アップサート」は
updatedExisting: false
のいずれかによって記録されました またはnull
方法によって結果が異なります。 -
updatedExisting: true
のいずれかを介して、ドキュメントが(組み合わせて)「存在する」ことを知っています。 または、ドキュメントが返される場所は「null
ではありません」でした "。 password
の場合 提供されたものは、username
にすでに存在するものと一致しませんでした 、次に「重複キーエラー」が発生し、それに応じてトラップして応答し、「パスワードが正しくありません」と応答してユーザーに通知します。
そのすべてが1つから リクエスト。
これが、単にコレクションに挿入をスローするのではなく、「アップサート」を使用する主な理由です。データベースに追加の要求を行わずにロジックのさまざまな分岐を取得して、これらの条件の「どれ」が実際の応答であるかを判断できるためです。