Menu
CoddyTech

عوامل التحديث

جزء من قسم قواعد البيانات باستخدام MongoDB في رحلة Node.js على Coddy. الدرس 15 من 29.

ينبغي أن تؤدي إضافة نسخة أو وسم إلى تغيير الكتاب في مكانه، دون قراءته وتغييره في JavaScript ثم كتابته مجددًا.

يضيف $inc عددًا إلى حقل، بدءًا من 0 عندما يكون الحقل غير موجود. ويضيف $push قيمةً إلى نهاية حقل مصفوفة، وينشئ المصفوفة عندما تكون غير موجودة. ويمكن أن يتضمن تحديث واحد عدة عوامل:

await players.updateOne(
    { name: 'Ada' },
    { $inc: { wins: 1 }, $push: { badges: 'streak' } }
);

تُطبّق قاعدة البيانات التغيير بنفسها، لذا يُحتسب كلا الطلبين عند ورودهما في اللحظة نفسها. لا يناسب $set هذه الحالة: فهو يستبدل مصفوفة badges بأكملها بالقيمة التي تعطيها له.

challenge icon

التحدي

سهل

يرصد الرف عدد نسخ كل كتاب تملكه، ويمكن للقراء إضافة وسوم إلى الكتب. يحدث كلا التغييرين داخل قاعدة البيانات، في عملية تحديث واحدة لكل منهما، من دون قراءة الكتاب أولًا.

  1. في server.js، أضف POST /books/:id/copies. لا يحتاج إلى جسم للطلب. يضيف 1 إلى الحقل copies في الكتاب. يصبح الحقل "copies": 1 في الكتاب الذي لا يحتوي على copies بعد.
  2. أضف POST /books/:id/tags. يحدد جسم الطلب وسمًا واحدًا، على سبيل المثال { "tag": "classic" }. يضيف المسار الوسم إلى نهاية قائمة tags في الكتاب، ويُبقي الوسوم الموجودة فيها. يصبح الحقل "tags": ["classic"] في الكتاب الذي لا يحتوي على tags بعد.
  3. يستخدم كلا المسارين parseId عند وجود معرّف غير صالح، وعندها تكون الاستجابة 400. ويردان برمز 404 عندما لا يوجد كتاب يحمل _id هذا. وفي غير ذلك، يرد كلاهما برمز الحالة 200 والكتاب المحدّث كاملًا. بعد إضافة نسختين والوسمين classic ثم favorite، يبدو Dune هكذا:
    { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi", "copies": 2, "tags": ["classic", "favorite"] }
    جرّب ذلك: أرسل GET /books وانسخ _id الخاص بـ Dune. أرسل POST /books/<Dune's _id>/copies مع جسم فارغ: ستحصل على Dune وبداخله "copies": 1. أرسل الطلب مرة أخرى: ستصبح القيمة "copies": 2. أرسل POST /books/<Dune's _id>/tags مع { "tag": "classic" }: ستحصل على "tags": ["classic"]. أرسل الطلب مرة أخرى مع { "tag": "favorite" }: ستحصل على "tags": ["classic", "favorite"]. أرسل POST /books/000000000000000000000000/copies: ستحصل على 404.

جرّب بنفسك

import express from 'express';
import { MongoClient, ObjectId } from 'mongodb';

const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('shelf');
const books = db.collection('books');

await books.insertMany([
    { title: 'Dune', author: 'Frank Herbert', year: 1965, isbn: '9780441013593', genre: 'scifi' },
    { title: 'Kindred', author: 'Octavia E. Butler', year: 1979, isbn: '9780807083697', genre: 'scifi' },
    { title: 'Emma', author: 'Jane Austen', year: 1815, isbn: '9780141439587', genre: 'classic' },
]);

const app = express();
app.use(express.json());

// تُرجع كل حالة خطأ { error: message }.
function sendError(res, status, message) {
    res.status(status).json({ error: message });
}

// القيمة :id بصيغة ObjectId، أو null بعد إرجاع 400 إذا كان المعرّف غير صالح.
function parseId(req, res) {
    if (ObjectId.isValid(req.params.id)) {
        return new ObjectId(req.params.id);
    }
    sendError(res, 400, 'Not a valid id');
    return null;
}

app.get('/', (req, res) => {
    res.send('Bookshelf API');
});

app.get('/books', async (req, res) => {
    const filter = {};
    if (req.query.author) {
        filter.author = req.query.author;
    }
    if (req.query.from || req.query.to) {
        filter.year = {};
        if (req.query.from) {
            filter.year.$gte = Number(req.query.from);
        }
        if (req.query.to) {
            filter.year.$lte = Number(req.query.to);
        }
    }
    const list = await books.find(filter).toArray();
    res.json(list);
});

app.post('/books', async (req, res) => {
    const book = req.body;
    await books.insertOne(book);
    res.status(201).json(book);
});

app.get('/books/newest', async (req, res) => {
    const n = Number(req.query.n) || 3;
    const list = await books
        .find({}, { projection: { title: 1, year: 1, _id: 0 } })
        .sort({ year: -1 })
        .limit(n)
        .toArray();
    res.json(list);
});

app.get('/books/:id', async (req, res) => {
    const id = parseId(req, res);
    if (!id) return;
    const book = await books.findOne({ _id: id });
    if (!book) {
        return sendError(res, 404, 'No such book');
    }
    res.json(book);
});

app.patch('/books/:id', async (req, res) => {
    const id = parseId(req, res);
    if (!id) return;
    const result = await books.updateOne({ _id: id }, { $set: req.body });
    if (result.matchedCount === 0) {
        return sendError(res, 404, 'No such book');
    }
    res.json(await books.findOne({ _id: id }));
});

app.listen(3000, () => {
    console.log('Bookshelf is running on port 3000');
});

جميع دروس قواعد البيانات باستخدام MongoDB

تدرّب بنفسك: Playground لـ Express