عمليات CRUD
تشير CRUD إلى أربع عمليات أساسية ينفّذها البرنامج على البيانات المخزّنة: الإنشاء (Create) والقراءة (Read) والتحديث (Update) والحذف (Delete). ومنها تُبنى معظم الميزات التي تتعامل مع البيانات المخزّنة. ففي قائمة جهات الاتصال، تُنشئ إضافةُ جهة اتصال صفاً في الجدول contacts، ويقرأ فتحُها ذلك الصف، ويحدّثه تغييرُ رقم الهاتف، وتحذفه إزالةُ جهة الاتصال، أو تعلّمه محذوفاً.
وفي تطبيق الويب تظهر كل عملية في ثلاث طبقات: إجراء المستخدم في الواجهة، وطلب HTTP إلى واجهة برمجة التطبيقات (API)، وجملة SQL في قاعدة البيانات. يربط هذا الدرس الطبقات الثلاث، لتنفّذ حيث يعمل المستخدم وتتأكد من النتيجة حيث تُخزَّن البيانات.
لماذا يهم
كثير من عيوب البيانات عيوب في عمليات CRUD: حقل يقبله النموذج ولا يُحفظ أبداً، وتعديل يغيّر السجل الخطأ أو يمسح حقلاً لم يلمسه أحد، وحذف يترك صفوفاً مرتبطة خلفه أو يحذف أكثر مما ينبغي. وكثيراً ما تخفيها الشاشة: فرسالة «تم الحفظ» الخضراء لا تثبت إلا أن الطلب انتهى دون خطأ، لا أن البيانات الصحيحة وصلت إلى الصف الصحيح.
وCRUD قائمة تغطية بسيطة أيضاً. لكل نوع من السجلات تتعامل معه الميزة اسأل: هل توجد العمليات الأربع؟ ومن يحق له تنفيذ كل منها؟ وهل اختُبرت كل منها؟ فالإنشاء والقراءة يُمارَسان في كل اختبار تقريباً لأن الاختبارات الأخرى تحتاج إلى بيانات، أما التحديث والحذف فلا يُختبران إلا إذا خطّط لهما أحد، وهما العمليتان اللتان قد تُضيّعان البيانات أو تكشفانها.
العمليات الأربع وما يجب أن تفعله كل منها
لكل عملية أثر متوقَّع على البيانات المخزّنة، وهذا الأثر هو ما تختبره. والمفتاح الأساسي (Primary key) من الدرس السابق يربط العمليات الأربع: الإنشاء يمنح السجل مفتاحه، والقراءة والتحديث والحذف تستخدمه لتصل إلى سجل واحد بالضبط.
- الإنشاء (Create): سجل جديد واحد بمفتاح أساسي جديد، وكل حقل مُرسَل في العمود الصحيح، مع القيم التي يضبطها النظام بنفسه، مثل الحالة الافتراضية و
created_at. - القراءة (Read): تُعاد البيانات المخزّنة كما هي، سجلاً واحداً بمفتاحه أو قائمة مرشَّحة، والقراءة لا تغيّر شيئاً.
- التحديث (Update): تتغيّر الحقول المقصودة في السجل المقصود، ويبقى كل ما عداها كما كان، ويتقدّم
updated_atإن وُجد في الجدول. - الحذف (Delete): يختفي السجل من المنتج. أما في قاعدة البيانات فإما أن يُزال (الحذف الفعلي، Hard delete)، أو يبقى ويُعلَّم، مثلاً بطابع زمني في
deleted_at(الحذف المنطقي، Soft delete). والحذف الفعلي لصف تشير إليه صفوف أخرى يتبع قاعدة الحذف في المفتاح الأجنبي التي تعلّمتها في الدرس السابق: تُحذف الصفوف المرتبطة معه (CASCADE)، أو تبقى ويصبح مرجعها NULL (SET NULL)، أو يُرفض الحذف (RESTRICTأوNO ACTION). أما الحذف المنطقي فهو تحديث، فلا تُطبَّق أي قاعدة حذف، وعلى التطبيق نفسه أن يعالج الصفوف المرتبطة.
عملية واحدة في ثلاث طبقات: الواجهة وHTTP وSQL
النقرة في الواجهة ترسل طلب HTTP إلى API، وتنفّذ API جملة SQL واحدة أو أكثر. ويحدد طلب HTTP طريقةً (Method) هي الإجراء المطلوب، ومساراً هو الهدف: فالطلب GET /contacts/7 يطلب جهة الاتصال 7، وتحمل الاستجابة رمز حالة مثل 200 OK. ويرسل PUT السجل الجديد كاملاً، بينما يرسل PATCH الحقول التي تتغيّر فقط. وفي جانب SQL تضيف INSERT صفوفاً، وتقرأ SELECT الصفوف، وتغيّر UPDATE قيماً في صفوف موجودة، وتحذف DELETE صفوفاً؛ ويقصر شرط WHERE أثر الجملة على الصفوف التي تستوفيه.
والربط المعتاد في واجهات REST، أي الواجهات المبنية على أسلوب شائع يسمّي فيه كل مسار سجلاً أو مجموعة من السجلات، هو: الإنشاء يقابله POST وINSERT، والقراءة GET وSELECT، والتحديث PUT أو PATCH وUPDATE، والحذف DELETE في الطبقتين. وهذه الجمل الأربع هي جوهر ما يُسمّى عادةً لغة معالجة البيانات (DML) في SQL. ويتعمّق درسا «طرق HTTP» و«أساسيات REST API» في الطلبات، ويشرح الدرس التالي جملة SELECT.
هذا الربط عُرف وليس قاعدة، ومواضع الاختلاف فيه هي حيث ينبغي أن تبحث الاختبارات. فطلب POST /orders واحد قد ينفّذ INSERT للطلب وآخر لكل صنف. وزر الحذف قد ينفّذ UPDATE يضبط deleted_at؛ فـRFC 9110 يعرّف DELETE بأنه إزالة الربط بين عنوان URI والمورد، لا وعداً بمسح البيانات. وزر الإلغاء قد يرسل POST /appointments/7/cancel، وهو تحديث بلغة CRUD. وعقد API أو المطور أو قاعدة البيانات يخبرك بما يحدث فعلاً.
| العملية | طلب HTTPعُرف REST | جملة SQL | الفحص في قاعدة البيانات |
|---|---|---|---|
| الإنشاءالواجهة: إضافة جهة اتصال | POST /contacts | INSERT INTO contacts … | صف جديد واحد بالضبط: معرّف جديد وكل الحقول وcreated_at مضبوط |
| القراءةالواجهة: فتح جهة الاتصال 7 | GET /contacts/7 | SELECT … FROM contacts WHERE id = 7 | الشاشة تطابق الصف، ولم يتغيّر شيء |
| التحديثالواجهة: تعديل الهاتف | PATCH /contacts/7 | UPDATE contacts SET phone = … WHERE id = 7 | تغيّر الهاتف في الصف 7 وحده؛ وتقدّم updated_at |
| الحذفالواجهة: حذف جهة الاتصال | DELETE /contacts/7 | DELETE FROM contacts WHERE id = 7أو UPDATE … SET deleted_at (حذف منطقي) | الصف أُزيل أو عُلِّم؛ والصفوف المرتبطة كما صُمّمت |
- المحاط بإطار: قد تنفّذ API جملة غير التي يوحي بها اسم العملية
تطبيق ويب معتاد بأسلوب REST: عقد API والشيفرة هما اللذان يحددان الربط الحقيقي، وقد ينفّذ طلب واحد عدة جمل.
النسخة النصية
CRUD UI action HTTP request SQL statement
------ --------------- ------------------ --------------------------------------------
Create Add contact POST /contacts INSERT INTO contacts (...) VALUES (...)
Read Open contact 7 GET /contacts/7 SELECT * FROM contacts WHERE id = 7
Update Edit phone PATCH /contacts/7 UPDATE contacts SET phone = ... WHERE id = 7
Delete Delete contact DELETE /contacts/7 DELETE FROM contacts WHERE id = 7تحقّق في المنتج أولاً، ثم في قاعدة البيانات
نفّذ كل عملية كما يفعل المستخدم أو العميل، عبر الواجهة أو API. فكتابة الصفوف مباشرة في قاعدة البيانات تتجاوز التحقق من صحة المدخلات ومنطق التطبيق اللذين يُفترض أنك تختبرهما، وجمل INSERT المباشرة لتجهيز الشروط المسبقة. ثم افحص النتيجة في النقاط أدناه. وفي الاختبارات السلبية، كالمدخلات غير الصالحة أو مستخدم بلا صلاحية، يجب ألا تُظهر قاعدة البيانات أي تغيير، ولا حتى صفاً جزئياً.
واعمل بأمان: استخدم SELECT للفحص، ولا تنفّذ INSERT أو UPDATE أو DELETE إلا في بيئة اختبار مسموح لك بتغييرها، ولا تنفّذها أبداً في بيئة الإنتاج دون إذن صريح. فجملة UPDATE أو DELETE بلا شرط WHERE تغيّر كل صفوف الجدول، لذا اكتب WHERE أولاً وشغّلها ضمن SELECT لترى الصفوف التي تطابقها.
- الاستجابة: الرسالة على الشاشة، أو رمز الحالة وجسم الاستجابة من API.
- القراءة اللاحقة عبر المنتج: أعد تحميل الصفحة أو أرسل طلب
GETمنفصلاً. فهي تكشف شاشة تكرّر مدخلاتك فقط وبيانات قديمة من الذاكرة المؤقتة (Cache)؛ ومقارنة الاستجابة قبل التحديث وبعده تُظهر كل حقل تغيّر. - قاعدة البيانات: استعلم عن الصف بمفتاحه الأساسي لترى ما قد لا يعرضه المنتج: ربط الحقول بالأعمدة، والقيم الافتراضية، والطوابع الزمنية، وعلامات الحذف المنطقي، والتقريب، وترميز النص العربي.
- ما يجاور الهدف: الإنشاء أضاف صفاً واحداً بالضبط؛ والتحديث لم يمسّ الصفوف الأخرى ولا الأعمدة غير المقصودة؛ والحذف عامل الصفوف المرتبطة كما صُمّم، والسجل اختفى من القوائم ونتائج البحث والمجاميع.
جرّبها: مختبِر REST API جرّبها: مقارنة JSON جرّبها: مدقّق استعلامات SQL
مثال تطبيقي: تغيير موعد
المتطلب: «يستطيع المريض نقل موعد قادم إلى فترة شاغرة أخرى لدى الطبيب نفسه، وتصبح الفترة القديمة شاغرة من جديد». هذه بالنسبة إلى المستخدم عملية تحديث واحدة، لكن في قاعدة البيانات ينبغي أن يتغيّر جدولان.
يسجّل المختبِر الحالة أولاً: الموعد 7 للمريض 1203 في الفترة 41 الساعة 09:00، وهو الموعد المحجوز الوحيد لهذا المريض. ثم يغيّر الموعد إلى الساعة 10:30 عبر الواجهة، فترسل طلب PATCH، ويعيد تحميل «مواعيدي» التي يجب أن تعرض 10:30، ويشغّل الاستعلامات في نهاية هذا القسم. اقرأ كلاً منها على أنه «اعرض هذه الأعمدة من الصفوف المطابقة»: فالعبارة IN (41, 58) تطابق أياً من المعرّفين، والدالة COUNT(*) تعيد عدد الصفوف المطابقة. ويكشف كل استعلام عيباً مختلفاً:
- الاستعلام 1: إن تغيّر
statusأوpatient_idفقد لمس التحديث أعمدة لا ينبغي أن يلمسها؛ وإن بقيslot_idعلى 41 فشاشة التأكيد لم تعرض إلا ما أُرسل. - الاستعلام 2: إن بقيت الفترة 41 محجوزة فلن يستطيع أحد حجز الساعة 09:00 مجدداً؛ وإن لم تُعلَّم الفترة 58 محجوزةً فقد يحجز مريض آخر الساعة 10:30 أيضاً. لا يرى المريض أي خلل، ولا يظهر ذلك إلا في قاعدة البيانات أو في ما يراه مريض آخر.
- الاستعلام 3: إن أدرجت API موعداً جديداً بدلاً من تحديث الصف 7 فالعدد 2، وهو حجز مزدوج لن تُظهره شاشة التأكيد.
-- Before: appointment 7 (patient 1203) is booked in slot 41 at 09:00
-- UI: "Reschedule" to 10:30 -> PATCH /api/appointments/7 {"slot_id": 58} -> 200 OK
-- 1. The appointment changed, and only the intended columns
SELECT id, patient_id, slot_id, status, updated_at
FROM appointments
WHERE id = 7;
-- expected: slot_id 58, patient_id 1203, status 'booked', updated_at later
-- 2. Both slots changed
SELECT id, starts_at, is_booked
FROM slots
WHERE id IN (41, 58);
-- expected: 41 is_booked false, 58 is_booked true
-- 3. No second appointment was created
SELECT COUNT(*)
FROM appointments
WHERE patient_id = 1203 AND status = 'booked';
-- expected: 1, the same as beforeأخطاء شائعة
- الثقة برسالة النجاح: فرسالة «تم الحفظ» أو
200 OKتقول إن الطلب انتهى، لا إن البيانات الصحيحة وصلت إلى الصف الصحيح. - اختبار الإنشاء والقراءة ونسيان التحديث والحذف، أو اختبار الحذف على سجلات بلا بيانات مرتبطة فقط.
- تغيير البيانات بجمل SQL وتسمية ذلك اختباراً للميزة؛ فالكتابة المباشرة لتجهيز الشروط المسبقة فقط.
- تنفيذ
UPDATEأوDELETEبلا شرطWHERE، أو في بيئة مشتركة، فتُتلف بيانات اختبار يعتمد عليها آخرون. - افتراض أن الربط واحد لواحد: توقّع اختفاء الصف والتصميم حذف منطقي، أو العكس. تأكّد أيّهما يقصده المتطلب.
من منظور ضمان الجودة
يبدأ التفكير بعمليات CRUD أثناء تحليل الاختبار. ومن الأدوات البسيطة لذلك مصفوفة CRUD (CRUD matrix): أنواع السجلات (جهات الاتصال والطلبات والفواتير) مقابل الأدوار أو الميزات التي تتعامل معها، وفي كل خلية C أو R أو U أو D. والخلايا الفارغة أو غير المتوقعة تتحول إلى أسئلة: من يحق له حذف فاتورة؟ هل الحذف فعلي أم منطقي؟ ماذا يحدث لبنود الفاتورة؟ واطلب مبكراً مخطط قاعدة البيانات وصلاحية قراءة فقط على قاعدة بيانات الاختبار.
وعند التنفيذ سجّل دليلاً من كل طبقة: الطلب والاستجابة، والاستعلام الذي شغّلته مع نتيجته قبل الإجراء وبعده. وفي تقرير الخطأ اذكر المفتاح الأساسي للسجل والاستعلام بالضبط والصفوف التي أعادها، مع إخفاء البيانات الشخصية، ليعيد المطور تشغيل الاستعلام بدلاً من تخمين السجل الذي تقصده.
تمرين عملي
خطّط فحوص CRUD للعناوين المحفوظة
يتيح متجر إلكتروني للعملاء حفظ عناوين التوصيل (المادة أدناه). لكل عملية من عمليات CRUD اكتب الإجراء في الواجهة، وطلب HTTP المتوقع، وجملة SQL المرجّحة، وفحصين على الأقل تجريهما في قاعدة البيانات بعد التنفيذ عبر المنتج. ثم اكتب سؤالاً واحداً لمالك المنتج أو للمطور.
الميزة: عناوين التوصيل المحفوظة
- يستطيع العميل إضافة عنوان، وعرض القائمة، وتعديل عنوان، وإزالة عنوان.
- عنوان واحد بالضبط من عناوين العميل هو العنوان الافتراضي.
- إزالة العنوان الافتراضي تجعل أحدث عنوان مضاف من العناوين المتبقية
هو الافتراضي.
- تحتفظ الطلبات بنسخة خاصة بها من العنوان؛ ويجب ألا تغيّر إزالة العنوان
الطلبات السابقة.
الجدول: addresses
id, customer_id, label, city, street, is_default,
created_at, updated_at, deleted_at
(قيمة deleted_at هي NULL ما دام العنوان نشطاً)- سمِّ العمليات الأربع والإجراء في الواجهة لكل منها.
- اكتب طريقة HTTP والمسار لكل منها وفق عُرف REST.
- اكتب جملة SQL المرجّحة، ودع تصميم الجدول يدلّك على الموضع الذي لا يكون فيه الحذف جملة
DELETEعادية. - اكتب فحوص قاعدة البيانات، بما فيها ما يجاور الصف المستهدف.
إظهار الإجابة النموذجية
الإنشاء: «إضافة عنوان» ← POST /addresses ← INSERT INTO addresses. الفحص: صف جديد واحد بالضبط لهذا customer_id، وكل حقل في عموده الصحيح (والنص العربي سليم)، وcreated_at مضبوط، وقيمة deleted_at هي NULL، وما زال للعميل عنوان افتراضي واحد بالضبط، وإن كان هذا أول عناوينه فيجب أن يكون هو الافتراضي.
القراءة: قائمة العناوين ← GET /addresses ← SELECT ... WHERE customer_id = ... AND deleted_at IS NULL. الفحص: القائمة تساوي الصفوف النشطة لهذا العميل، بلا عناوين مُزالة ولا عناوين لعملاء آخرين.
التحديث: «تعديل» ← PATCH /addresses/{id} (أو PUT بالعنوان كاملاً) ← UPDATE addresses SET ... WHERE id = .... الفحص: تغيّر هذا الصف والأعمدة المعدَّلة وحدها، وتقدّم updated_at ولم يتغيّر created_at؛ وجعلُه افتراضياً يزيل العلامة عن العنوان الافتراضي السابق.
الحذف: «إزالة» ← DELETE /addresses/{id} ← على الأرجح UPDATE addresses SET deleted_at = ...، أي حذف منطقي، لأن في الجدول عموداً اسمه deleted_at. الفحص: الصف ما زال موجوداً وقيمة deleted_at فيه مضبوطة، والعنوان اختفى من القائمة، والطلبات السابقة ما زالت تعرضه، وإن كان هو الافتراضي فالعنوان النشط صاحب أحدث created_at أصبح الافتراضي الوحيد.
أسئلة: هل يستطيع العميل إزالة عنوانه الأخير؟ هل تُمسح العناوين المُزالة نهائياً يوماً ما، ومتى؟ هل ترسل شاشة التعديل PUT أم PATCH؟
أسئلة شائعة
هل أحتاج إلى صلاحية على قاعدة البيانات لاختبار CRUD؟
ليس في كل فحص. يمكنك التحقق من كثير عبر المنتج: اقرأ السجل مرة أخرى في شاشة أخرى أو بطلب GET، وانظر إليه بحساب مستخدم آخر. لكن بعض الآثار لا تظهر إلا في قاعدة البيانات: علامات الحذف المنطقي والطوابع الزمنية والصفوف المرتبطة والتقريب والترميز. اطلب صلاحية قراءة فقط على قاعدة بيانات بيئة الاختبار، وحيث لا يتاح ذلك اتفق مع المطورين على طريقة فحص هذه الآثار.
هل الحذف المنطقي حذفٌ فعلاً؟
بالنسبة إلى المستخدم نعم: السجل اختفى من المنتج. أما في قاعدة البيانات فهو UPDATE يضبط علامة أو طابعاً زمنياً. اختبر الجانبين: السجل لم يعد يظهر في القوائم أو البحث أو المجاميع أو ملفات التصدير، والصف محفوظ أو يُمسح لاحقاً كما ينص المتطلب وأي قاعدة للاحتفاظ بالبيانات.
هل تربط كل API عمليات CRUD بالطرق POST وGET وPUT وPATCH وDELETE؟
لا. هذا عُرف REST المبني على معاني الطرق في RFC 9110 وRFC 5789. وكثير من واجهات API تمثّل الإجراءات بطلبات POST، مثل POST /orders/42/cancel، وواجهات GraphQL ترسل عادةً كل عملية بطلب POST. عقد API هو الذي يخبرك بالربط الحقيقي، واختباراتك تفحص الأثر على البيانات أياً كانت الطريقة.
المراجع
- RFC 9110: HTTP Semanticsمعاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.
- ISO/IEC 9075-1:2023 — Information technology — Database languages SQL — Part 1: Framework (SQL/Framework)الجزء الإطاري من معيار SQL الدولي، الذي تنفّذه لهجات PostgreSQL وMySQL وSQLite وغيرها بدرجات متفاوتة.
- SQL As Understood By SQLiteقواعد SQL التي يقبلها SQLite، عبارة تلو الأخرى، مع مخططات نحوية.
- RFC 5789: PATCH Method for HTTPأسلوب PATCH للتحديثات الجزئية، مقابل PUT الذي يستبدل المورد كاملاً.
تدرّب بأدوات متقن
اختبر نقاط نهاية REST بتحكم كامل في الطريقة والترويسات وجسم JSON، ثم أضف تأكيدات على الحالة والترويسات ومسارات JSON.
مقارنة مستندي JSON دلالياً وإبراز القيم المضافة والمحذوفة والمتغيرة.
فحص جمل SQL بحثاً عن مشكلات الصياغة والأقواس غير المتوازنة والسلاسل النصية غير المغلقة والأنماط الخطرة.
تنسيق استعلامات SQL بحالة أحرف متسقة للكلمات المفتاحية ومسافات بادئة وفواصل أسطر (MySQL وPostgreSQL وSQL Server وOracle وSQLite).
توليد جمل INSERT من تعريف جدول أو صفوف نموذجية أو CSV أو JSON.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.