IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis45/65-dars17 daqiqa
Mundarija (28)

Mongoose CRUD va update operatorlari: findOneAndUpdate, returnDocument va runValidators

Qisqacha: Mongoose'da o'qish — find, findOne, findById; yozish — create, updateOne, findOneAndUpdate, deleteOne. O'zgartirish drayverdagi kabi operatorlar bilan ($set, $inc, $push, $addToSet, $pull), lekin operatorsiz obyektni Mongoose o'zi $set ga o'raydi. findOneAndUpdate sukut bo'yicha eski hujjatni qaytaradi; yangisi uchun returnDocument: 'after' (Mongoose 9.11 da { new: true } eskirgan). Eng xavfli tuzoq: updateOne schema validatsiyasini ishlatmaydi — kanonda enum'ga zid juda bazaga yozildi; runValidators: true kerak.

Bu darsda

  • Mongoose so'rovlari bilan o'qiysiz: filtr, select, saralash, $in, $regex.
  • findOneAndUpdate ni returnDocument: 'after' bilan ishlatasiz va eskirgan new: true ni taniysiz.
  • Update operatorlarini Mongoose orqali qo'llaysiz, upsert va $setOnInsert ni bilasiz.
  • runValidators siz update validatsiyani chetlab o'tishini va pre("save") ishlamasligini tushunasiz.

Oldin bilishingiz kerak: Mongoose: schema va model, MongoDB native driver, UPDATE va DELETE xavfsiz, Read phenomena va izolyatsiya darajalari.

1. Nega bu kerak?

Oldingi darsda schema yozdik: ustuvorlik faqat past, orta, yuqori bo'la oladi. Kanon buni sinab, ustuvorlik: "juda" bilan create qildi — Mongoose rad etdi. Xotirjam bo'lsa bo'ladimi?

Yo'q. Kanon keyin xuddi shu qiymatni updateOne bilan yozdi — va u bazaga tushdi. Schema bor, qoida bor, lekin bitta metod uni chetlab o'tdi. Bu dars — Mongoose'ning har bir CRUD metodi aslida nima qilishi haqida. Qaysi biri validatsiyani ishga tushiradi, qaysi biri eski hujjatni qaytaradi, qaysi biri save middleware'ini chaqiradi.

O'xshatish: binoning asosiy eshigida qorovul bor — hujjatni tekshiradi. Lekin orqa eshik ham bor va u yerda qorovul yo'q. create va save — asosiy eshik. updateOne — orqa eshik. Qorovulni u yerga ham qo'yish mumkin, faqat buni o'zingiz aytishingiz kerak.

Chiqishlar — kanon tajribasidan (kanon o'lchovi, IlmHamroh, 2026-10): Node 24.21.0, mongoose 9.11.2, MongoDB 8.3.13. Schema — oldingi darsdagi VazifaS; v1 — "Non olish #uy #Bozor" vazifasi, aziz — Azizning hujjati. Kolleksiya nomlari vazifas, foydalanuvchis — oldingi darsdagi tuzoq.

2. O'qish

2.1 find va so'rov obyekti

js
await Vazifa.insertMany([
  { egasi: aziz._id, matn: "Hisobot yozish #ish",
    ustuvorlik: "yuqori" },
  { egasi: aziz._id, matn: "Kitob o'qish #o'qish" },
]);
const yuqorilar = await Vazifa
  .find({ ustuvorlik: { $in: ["yuqori", "past"] } })
  .select("matn -_id")
  .lean();
console.log("find $in:", yuqorilar.map((v) => v.matn));
text
find $in: [ 'Hisobot yozish #ish' ]

insertMany — bir nechta hujjatni bitta so'rovda; har biri schema bo'yicha tekshiriladi va default'lar qo'yiladi (ikkinchi vazifada ustuvorlik — orta).

Vazifa.find(…) darhol bazaga bormaydi. U so'rov (Query) obyekti qaytaradi. Uni zanjir bilan to'ldirasiz: .select(), .sort(), .limit(), .lean(). So'rov await qilinganda (yoki .exec() chaqirilganda) ketadi.

  • .select("matn -_id") — projection satr shaklida: matn ni ber, _id ni yashir. Obyekt shakli ham bor: .select({ matn: 1, _id: 0 }).
  • .lean() — Mongoose hujjati (metodlari, getter'lari bilan) o'rniga oddiy JS obyekt. Tezroq; Mongoose ilg'or darsida o'lchaymiz.

2.2 $regex va boshqa o'qish metodlari

js
const soni = (await Vazifa
  .find({ matn: { $regex: "KITOB", $options: "i" } })
  .lean()).length;
console.log("$regex:", soni);
text
$regex: 1

i — katta-kichik harf farqsiz. Bu yerda biz hujjatlarni olib, JavaScript'da sanadik. Faqat son kerak bo'lsa — Vazifa.countDocuments(filtr): hujjatlar tarmoqdan o'tmaydi.

Metod Qaytaradi Topilmasa
find(filtr) massiv []
findOne(filtr) bitta hujjat null
findById(id) bitta hujjat (_id bo'yicha) null
countDocuments(filtr) son 0
exists(filtr) { _id } yoki null null

findById(id) — findOne({ _id: id }) ning qisqasi. Id satr bo'lsa, Mongoose uni ObjectId'ga aylantiradi; aylanmasa — CastError (oldingi dars).

Diqqat: $regex ga foydalanuvchi kiritgan matnni to'g'ridan qo'ymang. .* yoki (a+)+ kabi naqsh butun kolleksiyani skanerlaydi yoki serverni qiynaydi (ReDoS). Matnni ekranlang yoki to'liq matn qidiruvidan foydalaning (MongoDB indekslari).

3. findOneAndUpdate: o'zgartirish va hujjatni olish

3.1 Muammo: qaysi hujjat qaytadi

Native driver darsida updateOne faqat sonlarni qaytardi (matchedCount, modifiedCount). API'da esa ko'pincha yangilangan hujjat kerak: PATCH /vazifalar/:id javobida yangi holat. Postgres'da UPDATE … RETURNING * edi.

Mongoose'da — findOneAndUpdate(filtr, ozgarish, sozlamalar). U hujjatni topadi, o'zgartiradi va qaytaradi. Qaysi holatini? Kanon ikkalasini ham ko'rsatdi.

3.2 Sukut: eski hujjat

js
const eski = await Vazifa.findOneAndUpdate(
  { _id: v1._id },
  { $set: { bajarildi: true } },
);
console.log("new'siz — ESKI hujjat qaytadi: bajarildi =",
  eski.bajarildi);
text
new'siz — ESKI hujjat qaytadi: bajarildi = false

Bazada bajarildi endi true. Lekin qaytgan hujjatda — false. Bu o'zgarishdan oldingi holat. Sukut shunday. Tajribasiz dasturchi buni ko'rib "update ishlamadi" deb o'ylaydi va kodni qayta-qayta tekshiradi.

3.3 Yangi hujjat: returnDocument: 'after'

Yangilangan hujjat uchun sozlama kerak. Uzoq yillar u { new: true } edi — xaritamizda ham, internetdagi ko'p darslarda ham shunday. Kanon aynan shu bilan ishga tushirdi va Mongoose 9.11.2 ogohlantirish berdi:

text
(node:6752) [MONGOOSE] Warning: mongoose: the `new` option for `findOneAndUpdate()` and `findOneAndReplace()` is deprecated. Use `returnDocument: 'after'` instead.
(Use `node --trace-warnings ...` to show where the warning was created)
findOneAndUpdate {new:true} → versiya 2 | izohlar 1 | teglar uy,bozor

Tarjima: "findOneAndUpdate() va findOneAndReplace() uchun new sozlamasi eskirgan (deprecated). O'rniga returnDocument: 'after' ishlating." (node:6752) — jarayon raqami, sizda boshqa bo'ladi.

Natija to'g'ri chiqdi (versiya 2, bitta izoh, teglar uy,bozor), lekin eskirgan sozlama keyingi katta versiyada o'chirilishi mumkin. Shuning uchun bu kursda asosiy shakl:

js
const yangi = await Vazifa.findOneAndUpdate(
  { _id: v1._id },
  {
    $inc: { versiya: 1 },
    $push: { izohlar: { muallif: aziz._id, matn: "Tez" } },
    $addToSet: { teglar: "uy" },
  },
  { returnDocument: "after" },
);
console.log(yangi.versiya, yangi.izohlar.length,
  yangi.teglar.join(","));

Ogohlantirish ayni shu almashtiruvni taklif qiladi, va returnDocument — MongoDB drayverining o'z sozlamasi nomi ("before" | "after"). Halol eslatma: kanon tajribasi new: true bilan ishlagan; returnDocument: 'after' li variantni biz bazada alohida ishga tushirmadik — natija Mongoose hujjati bo'yicha bir xil (versiya 2, izohlar 1, teglar uy,bozor). Bazasiz esa tekshirdik (mongoose 9.11.2): sozlama so'rovga {"returnDocument":"after"} bo'lib o'tadi va ogohlantirish chiqmaydi.

Maslahat: Eski loyihada { new: true } ni ko'rsangiz — u ishlaydi, faqat ogohlantirish beradi. Yangi kodda returnDocument: "after". Loglaringizda [MONGOOSE] Warning qatorlarini e'tiborsiz qoldirmang — ular keyingi yangilanishda nima buzilishini oldindan aytadi.

findByIdAndUpdate(id, …) — findOneAndUpdate({ _id: id }, …) ning qisqasi. findOneAndDelete — o'chirib, o'chirilgan hujjatni qaytaradi.

Tekshirib ko'ring: API'da PATCH /vazifalar/:id javobida findByIdAndUpdate(id, { $set: body }) natijasini qaytaryapsiz, returnDocument yo'q. Foydalanuvchi matnni o'zgartirdi. U ekranda nimani ko'radi?

Javob

Eski matnni. Bazada yangisi saqlangan, lekin javobda o'zgarishdan oldingi hujjat. Frontend javobni ekranga chiqarsa — foydalanuvchi "saqlanmadi" deb qayta-qayta bosadi. Yechim — { returnDocument: "after" }.

4. Update operatorlari Mongoose'da

4.1 Operatorlar — drayverdagidek

Operatorlar bazaning o'ziniki, Mongoose ularni o'zgarishsiz yuboradi (faqat qiymatlarni schema turiga aylantiradi):

Operator Misol Natija
$set { $set: { bajarildi: true } } maydon qiymati
$inc { $inc: { versiya: 1 } } songa qo'shish
$push { $push: { izohlar: {...} } } massiv oxiriga
$addToSet { $addToSet: { teglar: "uy" } } takrorsiz qo'shish
$pull { $pull: { teglar: "uy" } } massivdan olib tashlash
$unset { $unset: { muddat: 1 } } maydonni o'chirish
$setOnInsert upsert'da faqat yangi hujjat yaratilganda

$push bilan qo'shilgan izoh ham izoh schema'si (IzohS) bo'yicha turga aylantiriladi va o'z _id sini oladi.

Kanon $pull ni sinadi:

js
const r = await Vazifa.findByIdAndUpdate(
  v1._id, { $pull: { teglar: "uy" } },
  { returnDocument: "after" });
console.log("$pull:", r.teglar.join(","));
text
$pull: bozor

(Kanon bu qatorni — va pastdagi ikki findByIdAndUpdate misolini ham — new: true bilan ishga tushirgan va yuqoridagi ogohlantirishni olgan. Biz kodni darsning asosiy shakliga o'tkazdik — ma'nosi bir xil.)

4.2 Operatorsiz obyekt: Mongoose o'zi o'raydi

Native driver darsida updateOne(f, { bajarildi: true }) drayverda xato bergan edi: Update document requires atomic operators. Mongoose'da esa bu ishlaydi. Kanon matnni operatorsiz o'zgartirdi:

js
await Vazifa.findByIdAndUpdate(
  v1._id, { matn: "Boshqa matn #yangi" },
  { returnDocument: "after" });

Mongoose { matn: … } ni { $set: { matn: … } } ga aylantirib yuboradi — hujjatning qolgani saqlanadi. Qulay, lekin ikki xavf bor:

  1. Drayver va Mongoose kodi aralash loyihada bir xil ko'rinadigan qator turlicha ishlaydi.
  2. replaceOne / findOneAndReplace da bu o'rash yo'q — ular baribir butun hujjatni almashtiradi.

Shuning uchun Mongoose'da ham operatorni aniq yozish tavsiya qilinadi: kod o'qiganga tushunarli bo'ladi.

4.3 Upsert va $setOnInsert

js
const u = await Vazifa.updateOne(
  { matn: "Yangi upsert" },
  { $setOnInsert: { egasi: aziz._id } },
  { upsert: true },
);
console.log("upsert:", JSON.stringify(u.upsertedCount));
text
upsert: 1

$setOnInsert — faqat yangi hujjat yaratilganda qo'llanadi. Hujjat allaqachon bor bo'lsa, egasi o'zgarmaydi. Naqsh: "topilsa — tegma, topilmasa — shu qiymatlar bilan yarat". Filtrdagi matn ham yangi hujjatga kiradi.

Upsert'da ehtiyot bo'ling: filtrga mos noyob indeks bo'lmasa, ikki parallel upsert ikkita hujjat yaratishi mumkin — ikkalasi bir vaqtda "topilmadi" deb o'ylaydi. Bu MongoDB hujjatida yozilgan; kanon parallel upsert'ni sinamadi. Postgres'dagi ON CONFLICT ham noyob cheklovga tayanardi (UPSERT va MERGE).

5. Tuzoq: update validatsiyani chetlab o'tadi

5.1 runValidators siz

Kanonning eng muhim topilmasi shu bo'limda:

js
const yomon = await Vazifa.updateOne(
  { _id: v1._id }, { $set: { ustuvorlik: "juda" } });
const bazada = (await Vazifa.findById(v1._id).lean()).ustuvorlik;
console.log("runValidators'siz updateOne enum'ni TEKSHIRMADI:",
  JSON.stringify(yomon), "→ bazada:", bazada);
text
runValidators'siz updateOne enum'ni TEKSHIRMADI: {"acknowledged":true,"modifiedCount":1,"upsertedId":null,"upsertedCount":0,"matchedCount":1} → bazada: juda

modifiedCount: 1 — muvaffaqiyat. Bazada — juda. Schema'da enum bor, create uni rad etgan edi. updateOne esa tekshirmadi.

Nega? Mongoose'da validatsiya hujjat darajasida qurilgan: u to'liq hujjatni xotirada ko'rib, qoidalarni tekshiradi. updateOne esa hujjatni xotiraga yuklamaydi — u faqat "shu filtrga shu operatorlarni qo'lla" degan buyruqni bazaga yuboradi. Tekshiradigan hujjatning o'zi yo'q.

5.2 runValidators: true

js
await Vazifa.updateOne(
  { _id: v1._id },
  { $set: { ustuvorlik: "juda" } },
  { runValidators: true },
).then(() => "o'tdi", (x) => `${x.name}: ${x.message}`);
text
runValidators: true → ValidationError: Validation failed: ustuvorlik: juda — ustuvorlik emas

Endi Mongoose o'zgarishdagi maydonlarni schema qoidalari bilan tekshiradi va xato beradi — o'zbekcha enum xabarimiz bilan. Lekin bu "update validatorlari" to'liq hujjat validatsiyasi emas:

  • Faqat o'zgarishda bor maydonlar tekshiriladi ($set, $unset, $push, $addToSet va boshqalar).
  • Custom validator ichida this hujjat emas — so'rov (Query) obyekti. Boshqa maydonga qarab tekshiradigan validator kutilganidek ishlamaydi.

Bu nozik farqlar Mongoose hujjatidagi "Update Validators" bo'limida; kanon ulardan faqat enum holatini sinadi.

Ikkinchi band — biz bazasiz tekshirdik (mongoose 9.11.2): validator ichida this — Query. Amaliy qoida: hamma joyda yoqing. Buning uchun global sozlama bor. Biz uni ham bazasiz sinadik: yoqilgach, runValidators siz updateOne({…}, { $set: { u: "juda" } }) ham ValidationError berdi — so'rov bazaga yetib bormasdan:

js
mongoose.set("runValidators", true);

5.3 pre("save") ham ishlamaydi

Kanon schema'sida pre("save") middleware bor: matn o'zgarsa, teglarni matndan qayta ajratadi (Mongoose ilg'or darsida to'liq). Kanon matnni update bilan o'zgartirdi:

js
const r = await Vazifa.findByIdAndUpdate(
  v1._id, { matn: "Boshqa matn #yangi" },
  { returnDocument: "after" });
console.log("update'da pre('save') ishlamaydi — teglar o'zgarmadi:",
  r.teglar.join(","));
text
update'da pre('save') ishlamaydi — teglar o'zgarmadi: uy,bozor

Matn endi #yangi tegli, teglar esa eski: uy,bozor. Ma'lumot bir-biriga zid bo'lib qoldi. Sabab o'sha: save middleware hujjat saqlanganda ishlaydi, findByIdAndUpdate esa hujjatni saqlamaydi.

flowchart TD
  A["O'zgartirish kerak"] --> B{"Qaysi metod?"}
  B -->|"doc.save(), create()"| C["validate + pre save"]
  B -->|"updateOne, findOneAndUpdate"| D["to'g'ridan bazaga"]
  D --> E{"runValidators?"}
  E -->|ha| F["faqat o'zgargan maydonlar tekshiriladi"]
  E -->|yo'q| G["tekshiruvsiz"]
  C --> H["MongoDB"]
  F --> H
  G --> H

Diagrammaga qarang: chap yo'l — hujjat yo'li (to'liq tekshiruv va save middleware). O'ng yo'l — so'rov yo'li (tez, atomik, lekin tekshiruvsiz yoki qisman tekshiruv bilan).

5.4 Qaysi yo'lni tanlash

doc.save() updateOne / findOneAndUpdate
Bazaga murojaat 2 (o'qish + yozish) 1
Validatsiya to'liq faqat runValidators bilan, qisman
pre("save") ishlaydi ishlamaydi (query middleware alohida)
Bir vaqtdagi o'zgarish o'qish va yozish orasida boshqasi o'zgartirishi mumkin atomik ($inc yo'qolmaydi)

Ikkinchi ustunning atomikligi muhim. doc.versiya += 1; await doc.save() — ikki foydalanuvchi bir vaqtda qilsa, biri yo'qolishi mumkin (lost update). $inc bazada bajariladi — yo'qolmaydi. Shuning uchun hisoblagich va massivga qo'shishda update operatorlari, murakkab biznes qoidali tahrirda — save() (yoki update + runValidators + qoidani alohida tekshirish).

Tekshirib ko'ring: Kanon pre("save") da teglarni matndan ajratadi. API matnni findByIdAndUpdate bilan o'zgartiradi. Ikkita yechim ayting.

Javob
  1. Matnni o'zgartirishda doc = await Vazifa.findById(id); doc.matn = yangi; await doc.save() — pre("save") ishlaydi (lekin ikki murojaat va atomiklik yo'q).
  2. Update'ning o'zida teglarni ham hisoblab yuborish: $set: { matn, teglar: teglariniOl(matn) }; yoki shu mantiqni query middleware'ga (pre("findOneAndUpdate")) ham qo'shish (Mongoose ilg'or). Muhimi — qoida bitta funksiyada bo'lsin va ikkala yo'l ham uni chaqirsin.

6. O'chirish

Metod Nima qiladi
deleteOne(filtr) birinchi mosini o'chiradi, { deletedCount }
deleteMany(filtr) hammasini; deleteMany({}) — kolleksiyani bo'shatadi
findOneAndDelete(filtr) o'chiradi va o'chirilgan hujjatni qaytaradi
doc.deleteOne() shu hujjatni (document middleware ishlaydi)

vazifalar ilovasida o'chirish "Savat" orqali — ochirilgan sanasini $set qilish (Soft delete). Mongo'da ham shu naqsh: deleteOne emas, updateOne({ _id }, { $set: { ochirilgan: new Date() } }), o'qishda { ochirilgan: { $exists: false } }.

7. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Postgres'da: u yerda UPDATE … RETURNING, validatsiya Zod'da va bazadagi CHECK lar har yo'lni to'sadi. Commit yo'q. Kanon Mongoose'da sinagani (kanon o'lchovi, IlmHamroh, 2026-10):

  1. find + $in, $regex, select, lean.
  2. findOneAndUpdate: sukut eski hujjat, new: true — ogohlantirish bilan; shuning uchun bu darsda returnDocument: 'after' asosiy.
  3. runValidators siz enum buzildi (juda), bilan — ValidationError.
  4. Update'da pre("save") ishlamadi; $pull, $setOnInsert upsert.

Solishtiring: Postgres'da CHECK (ustuvorlik IN (…)) qaysi yo'l bilan yozilmasin — psql, pg, Prisma — to'xtatadi. Mongoose'da qoida faqat "asosiy eshik" da turadi.

8. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
findOneAndUpdate sozlamasiz Eski hujjat qaytadi { returnDocument: "after" }
{ new: true } yangi kodda Ogohlantirish, kelajakda buziladi returnDocument: "after"
updateOne + schema qoidasiga ishonish Yaroqsiz qiymat bazaga yoziladi runValidators: true (yoki global)
Update'da pre("save") ni kutish Hisoblangan maydonlar eskiradi save() yoki query middleware
doc.n += 1; doc.save() parallel Lost update $inc
Foydalanuvchi matni $regex ga ReDoS, butun skan Ekranlash yoki text indeks
findOneAndReplace ni update deb o'ylash Hujjat butunlay almashadi findOneAndUpdate + $set

9. Mashqlar

Mashqlar kurs/mashqlar/27/45-mongoose-crud/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik — Mongoose xulqining kichik modeli, kutubxonasiz).

1-mashq (oson): to'ldiring

  • findOneAndUpdate sukut bo'yicha qaysi hujjatni qaytaradi?
  • Yangi hujjatni qaytarish sozlamasi: { returnDocument: "" }
  • Update'da schema qoidalarini yoqadigan sozlama —
  • Faqat yangi hujjat yaratilganda qo'llanadigan operator —
  • Mongoose 9.11 da eskirgan sozlama: { : true }
Yechim
  • eski — o'zgarishdan oldingi holat (bajarildi = false).
  • after — returnDocument: "after"; drayver sozlamasining nomi.
  • runValidators — faqat o'zgargan maydonlar tekshiriladi.
  • $setOnInsert — upsert'da yangi hujjat uchun.
  • new — ogohlantirish: Use returnDocument: 'after' instead.

2-mashq (o'rta): returnDocument qanday ishlaydi

Xotiradagi "kolleksiya" uchun topibYangila(kolleksiya, filtr, ozgarish, sozlama) yozing. Filtr — faqat _id bo'yicha tenglik; o'zgarish — faqat $set va $inc. sozlama.returnDocument — "before" (sukut) yoki "after". Topilmasa null, sozlama.upsert bo'lsa — yangi hujjat yaratib, "after" da uni qaytarsin. Kolleksiyadagi hujjat haqiqatan o'zgarsin, qaytgan obyekt esa nusxa bo'lsin.

Ishora: structuredClone — chuqur nusxa.

Yechim
js
function topibYangila(kol, filtr, oz, sozlama = {}) {
  const { returnDocument = "before", upsert = false } = sozlama;
  let h = kol.find((x) => x._id === filtr._id);
  if (!h && !upsert) return null;
  const yangiMi = !h;
  if (yangiMi) {
    h = { _id: filtr._id };
    kol.push(h);
  }
  const oldin = yangiMi ? null : structuredClone(h);
  for (const [m, q] of Object.entries(oz.$set ?? {})) h[m] = q;
  for (const [m, q] of Object.entries(oz.$inc ?? {})) {
    h[m] = (h[m] ?? 0) + q;
  }
  return returnDocument === "after" ? structuredClone(h) : oldin;
}

const kol = [{ _id: 1, matn: "Non olish", bajarildi: false,
  versiya: 1 }];
const oz = { $set: { bajarildi: true }, $inc: { versiya: 1 } };

const a = topibYangila(kol, { _id: 1 }, oz);
console.log("sukut:", a.bajarildi, a.versiya);
const b = topibYangila(kol, { _id: 1 }, oz,
  { returnDocument: "after" });
console.log("after:", b.bajarildi, b.versiya);
console.log("topilmadi:", topibYangila(kol, { _id: 2 }, oz));
const c = topibYangila(kol, { _id: 2 }, oz,
  { upsert: true, returnDocument: "after" });
console.log("upsert:", JSON.stringify(c), "| jami:", kol.length);

Konsolda:

text
sukut: false 1
after: true 3
topilmadi: null
upsert: {"_id":2,"bajarildi":true,"versiya":1} | jami: 2

Ikkinchi chaqiruvda versiya 3: birinchi chaqiruv ham bazani (kolleksiyani) o'zgartirgan edi — faqat eski nusxani qaytargan. Kanondagi "bajarildi = false" holati aynan birinchi qator: o'zgarish bo'ldi, qaytgani eski. Upsert'da $inc yo'q maydonni 0 dan boshlaydi — MongoDB'da ham shunday.

3-mashq (qiyin): runValidators modeli

updateOne(kol, id, ozgarish, sxema, sozlama) yozing. O'zgarish — $set. sozlama.runValidators bo'lsa, $set dagi har maydon sxemadagi enum bo'yicha tekshirilsin; xato bo'lsa hech narsa yozilmasin va ValidationError: Validation failed: <maydon>: <xabar> matnli xato tashlansin. Aks holda — tekshiruvsiz yozilsin. Natija — { matchedCount, modifiedCount }. Kanon misolini takrorlang: ustuvorlik: "juda" avval tekshiruvsiz, keyin runValidators bilan.

Ishora: xabar — {VALUE} — ustuvorlik emas shablonidagi {VALUE} ni replace bilan almashtiring.

Yechim
js
const sxema = {
  ustuvorlik: {
    enum: ["past", "orta", "yuqori"],
    message: "{VALUE} — ustuvorlik emas",
  },
};

function updateOne(kol, id, oz, sxema, sozlama = {}) {
  const h = kol.find((x) => x._id === id);
  if (!h) return { matchedCount: 0, modifiedCount: 0 };
  const set = oz.$set ?? {};
  if (sozlama.runValidators) {
    const xatolar = [];
    for (const [m, q] of Object.entries(set)) {
      const r = sxema[m];
      if (r?.enum && !r.enum.includes(q)) {
        xatolar.push(`${m}: ${r.message.replace("{VALUE}", q)}`);
      }
    }
    if (xatolar.length) {
      const x = new Error(`Validation failed: ${xatolar.join(", ")}`);
      x.name = "ValidationError";
      throw x;
    }
  }
  let ozgardi = false;
  for (const [m, q] of Object.entries(set)) {
    if (h[m] !== q) { h[m] = q; ozgardi = true; }
  }
  return { matchedCount: 1, modifiedCount: ozgardi ? 1 : 0 };
}

const kol = [{ _id: 1, matn: "Non olish", ustuvorlik: "orta" }];
const oz = { $set: { ustuvorlik: "juda" } };
console.log(JSON.stringify(updateOne(kol, 1, oz, sxema)),
  "→ bazada:", kol[0].ustuvorlik);
kol[0].ustuvorlik = "orta";
try {
  updateOne(kol, 1, oz, sxema, { runValidators: true });
} catch (x) {
  console.log(`${x.name}: ${x.message}`);
}
console.log("bazada:", kol[0].ustuvorlik);

Konsolda:

text
{"matchedCount":1,"modifiedCount":1} → bazada: juda
ValidationError: Validation failed: ustuvorlik: juda — ustuvorlik emas
bazada: orta

Ikkinchi qator kanondagi haqiqiy Mongoose xabari bilan aynan bir xil. Modelning asosiy g'oyasi: update validatori faqat o'zgarishdagi maydonlarni ko'radi — butun hujjatni emas. matn yaroqsiz bo'lsa ham, $set da yo'q ekan, tekshirilmaydi.

10. Real ishda

  • runValidators: true — ko'p jamoalarda global yoqiladi, chunki "update orqali yaroqsiz ma'lumot" — Mongoose loyihalarining klassik xatosi.
  • returnDocument: 'after' — PATCH endpoint'lari; eski loyihalarni yangilashda new: true ni almashtirish oddiy, lekin foydali ish.
  • Hisoblagichlar va massivlar (likes, views, teglar) — doim $inc / $addToSet; "o'qi-o'zgartir-saqla" — poyga holati.
  • Intervyu savollari: "save() va updateOne() farqi?", "nega update validatsiyani o'tkazib yuboradi?", "findOneAndUpdate nimani qaytaradi?".

Xulosa

  • Mongoose so'rovi — Query obyekti: await qilinganda ketadi; select, sort, limit, lean bilan to'ldiriladi.
  • findOneAndUpdate sukut bo'yicha eski hujjatni qaytaradi; yangisi — returnDocument: 'after'; { new: true } Mongoose 9.11 da eskirgan.
  • Operatorlar drayverdagidek; operatorsiz obyektni Mongoose $set ga o'raydi (drayver — rad etadi).
  • updateOne/findOneAndUpdate schema validatsiyasini ishlatmaydi: kanonda juda bazaga yozildi. runValidators: true faqat o'zgargan maydonlarni tekshiradi.
  • Update'da pre("save") ishlamaydi — hisoblangan maydonlar eskiradi.
  • Hisoblagich va massivga — atomik operator; murakkab qoidaga — save() yoki qoidani ikkala yo'lda ham chaqirish.

Keyingi dars: Populate va embed vs reference qarori — Mongo'da bog'lanishning ikki yo'li, populate qancha so'rov yuborishi va qachon nimani tanlash.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Mongoose CRUD va update operatorlari: findOneAndUpdate, returnDocument va runValidators — IlmHamroh