Discriminator — bir collection, bir nechta schema

~20 daq · MongoDB · Mongoose — Ilg'or

Muammodan boshlaylik

Bildirishnomalar tizimi. Uch xil bildirishnoma:

  • Buyurtma — buyurtmaId, holat
  • Izoh — postId, izohMatni, kimdan
  • Tizim — daraja (info/warning), havola

Hammasida umumiy: user, oqildi, createdAt.

Variant 1 — uchta collection: "foydalanuvchining barcha bildirishnomalari, vaqt bo'yicha" — uchta so'rov va birlashtirish.

Variant 2 — bitta schema, barcha maydonlar ixtiyoriy: validatsiya yo'q, buyurtma bildirishnomasida postId bo'lishi ham mumkin.


Discriminator nima?

Discriminator — bitta collection da bir nechta schema. Har bir document qaysi turga tegishli ekanini maxsus maydon (__t yoki o'zingiz bergan nom) belgilaydi.

bildirishnomalar collection
├── { tur: "BuyurtmaBildirishnoma", user, oqildi, buyurtmaId, holat }
├── { tur: "IzohBildirishnoma",     user, oqildi, postId, izohMatni, kimdan }
└── { tur: "TizimBildirishnoma",    user, oqildi, daraja, havola }
  • Umumiy maydonlar — asosiy schema da
  • Har bir turning o'z maydonlari va validatsiyasi
  • Bitta so'rov bilan hammasini olish mumkin

Hayotiy o'xshatma

Pochta bo'limi. Xatlar, posilkalar va telegrammalar bitta javonda saqlanadi — hammasida qabul qiluvchi va sana bor. Lekin har birida o'z blankasi: posilkada og'irlik, telegrammada so'zlar soni. Har bir buyumga yopishtirilgan rangli yorliq (discriminator kaliti) — uning turini bildiradi.


Yaratish

import mongoose from 'mongoose'
const { Schema } = mongoose

// 1. Asosiy schema
const bildirishnomaSchema = new Schema({
  user: { type: Schema.Types.ObjectId, ref: 'User', required: true, index: true },
  oqildi: { type: Boolean, default: false },
}, {
  timestamps: true,
  discriminatorKey: 'tur',
  collection: 'bildirishnomalar',
})

export const Bildirishnoma = mongoose.model('Bildirishnoma', bildirishnomaSchema)

// 2. Turlar
export const BuyurtmaBildirishnoma = Bildirishnoma.discriminator('BuyurtmaBildirishnoma', new Schema({
  buyurtmaId: { type: Schema.Types.ObjectId, ref: 'Buyurtma', required: true },
  holat: { type: String, enum: ['qabul', 'yolda', 'yetkazildi'], required: true },
}))

export const IzohBildirishnoma = Bildirishnoma.discriminator('IzohBildirishnoma', new Schema({
  postId: { type: Schema.Types.ObjectId, ref: 'Post', required: true },
  kimdan: { type: Schema.Types.ObjectId, ref: 'User', required: true },
  izohMatni: { type: String, maxlength: 200 },
}))

export const TizimBildirishnoma = Bildirishnoma.discriminator('TizimBildirishnoma', new Schema({
  daraja: { type: String, enum: ['info', 'warning'], default: 'info' },
  matn: { type: String, required: true },
  havola: String,
}))

discriminatorKey: 'tur' — standart __t o'rniga tushunarli nom.


Ishlatish

Yaratish

await BuyurtmaBildirishnoma.create({ user: mijozId, buyurtmaId, holat: 'yolda' })
// bazada: { tur: "BuyurtmaBildirishnoma", user, buyurtmaId, holat: "yolda", oqildi: false, ... }

await BuyurtmaBildirishnoma.create({ user: mijozId, holat: 'yolda' })
// ValidationError: buyurtmaId: Path `buyurtmaId` is required.

Har bir tur o'z validatsiyasiga ega.

O'qish

// barcha turlar — asosiy model orqali
const hammasi = await Bildirishnoma.find({ user: userId }).sort({ createdAt: -1 }).limit(20).lean()

// faqat bitta tur — avtomatik { tur: "IzohBildirishnoma" } filtri qo'shiladi
const izohlar = await IzohBildirishnoma.find({ user: userId })
// frontend da tur bo'yicha ko'rsatish
for (const b of hammasi) {
  switch (b.tur) {
    case 'BuyurtmaBildirishnoma': /* ... */ break
    case 'IzohBildirishnoma': /* ... */ break
  }
}

Umumiy amallar

// barcha turlarni o'qilgan deb belgilash — bitta so'rov
await Bildirishnoma.updateMany({ user: userId, oqildi: false }, { oqildi: true })

// o'qilmaganlar soni
await Bildirishnoma.countDocuments({ user: userId, oqildi: false })

Turga xos metodlar va middleware

const izohSchema = new Schema({ ... })

izohSchema.methods.xabarMatni = function () {
  return `Postingizga yangi izoh: "${this.izohMatni}"`
}

izohSchema.pre('save', function () {
  if (this.izohMatni?.length > 200) this.izohMatni = this.izohMatni.slice(0, 197) + '...'
})

export const IzohBildirishnoma = Bildirishnoma.discriminator('IzohBildirishnoma', izohSchema)

Asosiy schema dagi middleware va metodlar barcha turlarga meros bo'lib o'tadi.

[!INFO] Ichki massivlar uchun ham discriminator bor: masalan, sahifa tarkibi bloklar: [blokSchema] — matn bloki, rasm bloki, video bloki. schema.path('bloklar').discriminator('Rasm', rasmSchema).


Qachon discriminator

✅ Mos:

  • Turlar umumiy maydonlarga ega va ko'pincha birga so'raladi (lenta, bildirishnomalar, to'lovlar, hodisalar)
  • Turlar soni cheklangan va barqaror

❌ Mos emas:

  • Turlar butunlay har xil va hech qachon birga so'ralmaydi — alohida collection lar oddiyroq
  • Har tur uchun index lar juda farq qiladi va collection ulkan

[!TIP] Index lar butun collection uchun umumiy. Faqat bitta turga tegishli maydonga index kerak bo'lsa — partialFilterExpression: { tur: 'IzohBildirishnoma' } bilan partial index (27-dars) yarating.


Tez-tez uchraydigan xatolar

Turni asosiy model orqali yaratish

// ❌ — turga xos maydonlar schema da yo'q, jim tashlab yuboriladi
await Bildirishnoma.create({ tur: 'IzohBildirishnoma', postId, izohMatni })

// ✅
await IzohBildirishnoma.create({ postId, izohMatni, ... })

Discriminator kalitini qo'lda o'zgartirish

tur ni update da o'zgartirish — document boshqa schema ga "o'tadi" va eski turga xos maydonlar noto'g'ri bo'lib qoladi. Tur o'zgarmas deb hisoblang.

Turlar uchun alohida collection opsiyasi

Discriminator har doim asosiy model collection ida — turga alohida collection berib bo'lmaydi.


Xulosa

  • Discriminator — bitta collection da bir nechta schema; tur maxsus kalit bilan belgilanadi
  • Asosiy schema — umumiy maydonlar; Model.discriminator('Nom', schema) — turlar
  • discriminatorKey — __t o'rniga tushunarli nom
  • Asosiy model — barcha turlar; tur modeli — avtomatik filtr va o'z validatsiyasi
  • Middleware va metodlar meros bo'ladi; turga xos index — partial index
  • Birga so'raladigan, umumiy maydonli turlar uchun

Keyingi dars: 52-dars — MongoDB Atlas — cloud database, cluster yaratish