Virtuals — virtual field, virtual populate
Muammodan boshlaylik
User da ism va familiya alohida saqlanadi. Ko'p joyda to'liq ism kerak:
`${user.ism} ${user.familiya}` // 15 ta faylda takrorlangan
Bazaga toliqIsm maydonini qo'shsangiz — ism o'zgarganda uni ham yangilashni unutasiz va ular farqlanib qoladi.
Ikkinchi muammo: user ning postlari. Userda postlar: [ObjectId] massivi saqlansa — har bir yangi post uchun userni ham yangilash kerak, massiv esa cheksiz o'sadi.
Virtual nima?
Virtual — bazada saqlanmaydigan, lekin document da maydon kabi ko'rinadigan xususiyat. Uning qiymati boshqa maydonlardan hisoblanadi.
const userSchema = new Schema({
ism: String,
familiya: String,
tugilgan: Date,
})
userSchema.virtual('toliqIsm').get(function () {
return `${this.ism} ${this.familiya}`
})
userSchema.virtual('yosh').get(function () {
if (!this.tugilgan) return null
const farq = Date.now() - this.tugilgan.getTime()
return Math.floor(farq / (365.25 * 24 * 60 * 60 * 1000))
})
const user = await User.findById(id)
user.toliqIsm // "Lola Karimova"
user.yosh // 24
Bazada faqat ism, familiya, tugilgan. Hech narsa takrorlanmaydi va farqlanib qolmaydi.
Hayotiy o'xshatma
Pasportda tug'ilgan sana yozilgan — yosh emas. Yoshni har safar sanadan hisoblaysiz. Agar pasportga "24 yosh" deb yozilsa — keyingi yili u noto'g'ri bo'lib qoladi. Virtual — "yoshni har safar sanadan hisoblash" qoidasi.
Setter
userSchema.virtual('toliqIsm')
.get(function () {
return `${this.ism} ${this.familiya}`
})
.set(function (v) {
const [ism, ...qolgan] = v.trim().split(' ')
this.ism = ism
this.familiya = qolgan.join(' ')
})
const user = new User({ toliqIsm: 'Lola Karimova' })
user.ism // "Lola"
user.familiya // "Karimova"
JSON da ko'rsatish
Standart holatda virtual lar res.json(user) da ko'rinmaydi:
const userSchema = new Schema({ ... }, {
toJSON: { virtuals: true },
toObject: { virtuals: true },
})
lean() va virtual lar
const user = await User.findById(id).lean()
user.toliqIsm // undefined — lean oddiy obyekt
.lean() bilan virtual lar kerak bo'lsa — mongoose-lean-virtuals plagini:
import mongooseLeanVirtuals from 'mongoose-lean-virtuals'
userSchema.plugin(mongooseLeanVirtuals)
await User.findById(id).lean({ virtuals: true })
[!WARNING] Virtual lar bo'yicha so'rov va saralash mumkin emas:
User.find({ toliqIsm: 'Lola Karimova' })— hech narsa topmaydi, chunki bazada bunday maydon yo'q. Filtr kerak bo'lsa — asl maydonlar bo'yicha qidiring yoki maydonni haqiqatan saqlang.
Virtual populate
Teskari bog'lanish: post userni biladi (muallif), user esa postlari haqida hech narsa saqlamaydi. Lekin userning postlarini populate qilish mumkin:
const postSchema = new Schema({
sarlavha: String,
muallif: { type: Schema.Types.ObjectId, ref: 'User', index: true },
nashr: Boolean,
})
userSchema.virtual('postlar', {
ref: 'Post', // qaysi model
localField: '_id', // userdagi maydon
foreignField: 'muallif', // Post dagi maydon
})
const user = await User.findById(id).populate({
path: 'postlar',
select: 'sarlavha createdAt',
match: { nashr: true },
options: { sort: { createdAt: -1 }, limit: 10 },
})
user.postlar // [{ sarlavha: "...", createdAt: ... }, ...]
- Userda massiv saqlanmaydi — cheksiz o'sish va sinxronlash muammosi yo'q
- Post yaratilganda faqat
Postyoziladi foreignFieldga index qo'ying
Sanash
userSchema.virtual('postlarSoni', {
ref: 'Post',
localField: '_id',
foreignField: 'muallif',
count: true,
})
const user = await User.findById(id).populate('postlarSoni')
user.postlarSoni // 42
Bitta document
userSchema.virtual('profil', {
ref: 'Profil',
localField: '_id',
foreignField: 'user',
justOne: true, // massiv emas, bitta document yoki null
})
Qachon virtual, qachon saqlash
| Virtual | Saqlangan maydon |
|---|---|
| Boshqa maydonlardan arzon hisoblanadi | Hisoblash qimmat (masalan, ko'p documentdan yig'indi) |
| Faqat ko'rsatish uchun | Filtrlash, saralash, index kerak |
| Doim yangi bo'lishi kerak (yosh) | Bir lahzadagi qiymat (buyurtma paytidagi narx) |
// ✅ virtual — arzon va doim to'g'ri
toliqIsm, yosh, chegirmaFoizi
// ✅ saqlangan — saralash va filtr kerak
reyting (o'rtacha baho), postlarSoni (ro'yxatda saralash uchun)
[!TIP] Saralash kerak bo'lgan hisoblangan maydonni saqlash va uni middleware (46-dars) yoki
$incbilan yangilab borish — keng tarqalgan yondashuv: masalan,post.izohlarSonihar yangi izohda$inc: 1.
Tez-tez uchraydigan xatolar
Arrow function
// ❌ — this undefined
userSchema.virtual('toliqIsm').get(() => `${this.ism} ${this.familiya}`)
toJSON: { virtuals: true } ni unutish
API javobida virtual yo'q — "lekin konsolda bor edi!".
select da virtual uchun kerakli maydonni chiqarib tashlash
await User.findById(id).select('email')
// toliqIsm → "undefined undefined"
Virtual hisoblanadigan maydonlar ham tanlangan bo'lishi kerak.
Xulosa
- Virtual — bazada saqlanmaydigan, boshqa maydonlardan hisoblanadigan xususiyat
schema.virtual('nom').get(function () {...}).set(...)— oddiy function- JSON da ko'rinishi uchun —
toJSON: { virtuals: true };lean()da — plagin - Virtual bo'yicha filtr va saralash mumkin emas
- Virtual populate — teskari bog'lanish massiv saqlamasdan:
ref,localField,foreignField count: true— sanash,justOne: true— bitta document
Keyingi dars: 46-dars — Middleware (Hooks) — pre, post, save, find, remove