النماذج

تم بناء نماذج KawkabJS على نمط السجل النشط.

تجعل طبقة نماذج البيانات في Kawkab من السهل جدًا تنفيذ عمليات CRUD وإدارة العلاقات بين النماذج.

نوصي باستخدام النماذج بشكل مكثف واللجوء إلى منشئ الاستعلامات القياسي لحالات الاستخدام الخاصة.

إنشاء النموذج الأول الخاص بك

لنلقِ نظرة على فئة نموذج أساسية ونناقش بعض الاتفاقيات الرئيسية في Kawkab:

const { Model } from "kawkab";
 
class Flight extends Model {
  //
}

أسماء الجداول

بعد إلقاء نظرة على المثال أعلاه، قد تكون لاحظت أننا لم نخبر Kawkab بأي جدول قاعدة بيانات يتوافق مع نموذج Flight الخاص بنا. وفقًا للاتفاقية، سيتم استخدام الاسم الجمعي للفئة بصيغة “snake case” كاسم للجدول ما لم يتم تحديد اسم آخر بشكل صريح. لذلك، في هذه الحالة، سيفترض Kawkab أن نموذج Flight يخزن السجلات في جدول flights، بينما سيخزن نموذج AirTrafficController السجلات في جدول air_traffic_controllers.

إذا لم يكن جدول قاعدة البيانات المقابل لنموذجك يتناسب مع هذه الاتفاقية، يمكنك تحديد اسم جدول النموذج يدويًا عن طريق تعريف خاصية الجدول على النموذج:

import { BaseModel } from "kawkab";
 
class Flight extends Model {
  // الجدول المرتبط بالنموذج.
  table = 'my_flights';
}

المفاتيح الأساسية

سيفترض Kawkab أيضًا أن كل جدول قاعدة بيانات مرتبط بنموذج يحتوي على عمود مفتاح أساسي يسمى id. إذا لزم الأمر، يمكنك تعريف خاصية primaryKey محمية على النموذج الخاص بك لتحديد عمود مختلف يعمل كمفتاح أساسي لنموذجك:

import { BaseModel } from "kawkab";
 
class Flight extends Model {
  // المفتاح الأساسي المرتبط بالجدول.
  primaryKey = 'flight_id';
}

إذا كنت ترغب في استخدام مفتاح أساسي غير متزايد أو غير رقمي، يجب عليك تعريف خاصية incrementing على النموذج الخاص بك والتي تكون مضبوطة على false:

class Flight extends Model {
  // يشير إلى ما إذا كان معرف النموذج يتزايد تلقائيًا.
  incrementing = false;
}

إذا لم يكن المفتاح الأساسي لنموذجك عددًا صحيحًا، يجب عليك تعريف خاصية keyType على النموذج الخاص بك. يجب أن تحتوي هذه الخاصية على قيمة سلسلة:

class Flight extends Model {
  // نوع البيانات للمعرف المتزايد تلقائيًا.
  keyType = 'string';
}

الطوابع الزمنية

بشكل افتراضي، يتوقع Sutando وجود أعمدة created_at و updated_at في جدول قاعدة البيانات المقابل لنموذجك. سيقوم Sutando تلقائيًا بتعيين قيم هذه الأعمدة عند إنشاء النماذج أو تحديثها. إذا كنت لا تريد أن يتم إدارة هذه الأعمدة تلقائيًا بواسطة Sutando، يجب عليك تعريف خاصية timestamps على النموذج الخاص بك بقيمة false:

import { BaseModel } from "kawkab";
 
class Flight extends Model {
  // يشير إلى ما إذا كان يجب وضع الطوابع الزمنية على النموذج.
  timestamps = false;
}

إذا كنت بحاجة إلى تخصيص أسماء الأعمدة المستخدمة لتخزين الطوابع الزمنية، يمكنك تعيين خصائص CREATED_AT و UPDATED_AT على النموذج الخاص بك:

import { BaseModel } from "kawkab";
 
class Flight extends Model {
  static CREATED_AT = 'creation_date';
  static UPDATED_AT = 'updated_date';
}

اتصالات قاعدة البيانات

بشكل افتراضي، ستستخدم جميع نماذج Sutando اتصال قاعدة البيانات default الذي تم تكوينه لتطبيقك. إذا كنت ترغب في تحديد اتصال مختلف يجب استخدامه عند التفاعل مع نموذج معين، يجب عليك تعريف خاصية connection على النموذج:

import { BaseModel } from "kawkab";
 
class Flight extends Model {
  connection = 'sqlite';
}

القيم الافتراضية للسمات

بشكل افتراضي، لن يحتوي مثيل النموذج الذي تم إنشاؤه حديثًا على أي قيم سمات. إذا كنت ترغب في تحديد القيم الافتراضية لبعض سمات النموذج الخاص بك، يمكنك تعريف خاصية attributes على النموذج الخاص بك. يجب أن تكون قيم السمات الموضوعة في attributes في شكلها الخام، “القابل للتخزين” كما لو كانت قد قرئت للتو من قاعدة البيانات:

import { BaseModel } from "kawkab";
 
class Flight extends Model {
  attributes = {
    options: '[]',
    delayed: false,
  };
}

استرجاع النماذج

بمجرد إنشاء نموذج وجدول قاعدة البيانات المرتبط به، تكون جاهزًا لبدء استرجاع البيانات من قاعدة البيانات الخاصة بك. يمكنك التفكير في كل نموذج Sutando كمنشئ استعلام قوي يتيح لك استعلام جدول قاعدة البيانات المرتبط بالنموذج بشكل سلس. ستسترجع طريقة all الخاصة بالنموذج جميع السجلات من جدول قاعدة البيانات المرتبط بالنموذج:

const { Flight } = require('./models');
 
const flights = await Flight.query().all();
 
flights.map(flight => {
  console.log(flight.name)
})

بناء الاستعلامات

ستعيد طريقة Sutando all جميع النتائج في جدول النموذج. ومع ذلك، نظرًا لأن كل نموذج Sutando يعمل كمنشئ استعلام، يمكنك إضافة قيود إضافية على الاستعلامات ثم استدعاء طريقة get/first/find لاسترجاع النتائج:

const flights = await Flight.query().where('active', 1)
  .orderBy('name')
  .take(10)
  .get();
 
const flight = await Flight.query().where('active', 1).first();
 
const flight = await Flight.query().find(5);

تحديث النماذج

إذا كان لديك بالفعل مثيل لنموذج Sutando تم استرجاعه من قاعدة البيانات، يمكنك “تحديث” النموذج باستخدام طرق fresh و refresh. ستعيد طريقة fresh استرجاع النموذج من قاعدة البيانات. لن يتأثر مثيل النموذج الحالي:

const flight = await Flight.query().where('number', 'FR 900').first();
 
const freshFlight = await flight.fresh();

ستعيد طريقة refresh تحديث النموذج الحالي باستخدام بيانات جديدة من قاعدة البيانات. بالإضافة إلى ذلك، سيتم تحديث جميع العلاقات المحملة أيضًا:

const flight = await Flight.query().where('number', 'FR 900').first();
 
flight.number = 'FR 456';
 
await flight.refresh();
 
flight.number; // "FR 900"

المجموعات

كما رأينا، تسترجع طرق Sutando مثل all و get سجلات متعددة من قاعدة البيانات. ومع ذلك، لا تعيد هذه الطرق مصفوفة عادية. بدلاً من ذلك، يتم إرجاع مثيل من Collection.

تمتد فئة Collection الخاصة بـ Sutando فئة collect.js التي توفر مجموعة متنوعة من الطرق المفيدة للتفاعل مع مجموعات البيانات. على سبيل المثال، يمكن استخدام طريقة reject لإزالة النماذج من مجموعة بناءً على نتائج إغلاق مستدعى:

const flights = await Flight.query().where('destination', 'Paris').get();
 
const newFlights = flights.reject(flight => {
  return flight.cancelled;
});

بالإضافة إلى الطرق التي توفرها فئة مجموعة collect.js الأساسية، توفر فئة مجموعة Sutando بعض الطرق الإضافية التي تهدف خصيصًا للتفاعل مع مجموعات نماذج Sutando.

نظرًا لأن جميع مجموعات Sutando تنفذ واجهات جافا سكريبت القابلة للتكرار، يمكنك التكرار عبر المجموعات كما لو كانت مصفوفة:

for (let flight of flights) {
  console.log(flight.name);
}

تقسيم النتائج

قد ينفد الذاكرة في تطبيقك إذا حاولت تحميل عشرات الآلاف من سجلات Sutando عبر طرق all أو get. بدلاً من استخدام هذه الطرق، يمكن استخدام طريقة chunk لمعالجة عدد كبير من النماذج بشكل أكثر كفاءة.

ستسترجع طريقة chunk مجموعة فرعية من نماذج Sutando، تمريرها إلى إغلاق للمعالجة. نظرًا لأنه يتم استرجاع جزء الحالي فقط من نماذج Sutando في كل مرة، ستوفر طريقة chunk استخدام ذاكرة منخفض بشكل كبير عند التعامل مع عدد كبير من النماذج:

const { Flight } = require('./models');
 
await Flight.query().chunk(200, flights => {
  flights.map(flight => {
    //
  });
});

استرجاع النماذج الفردية / المجاميع

بالإضافة إلى استرجاع جميع السجلات التي تطابق استعلامًا معينًا، يمكنك أيضًا استرجاع السجلات الفردية باستخدام طرق find أو first. بدلاً من إعادة مجموعة من النماذج، تعيد هذه الطرق مثيل نموذج واحد:

const { Flight } = require('./modles');
 
// استرجاع نموذج بواسطة مفتاحه الأساسي...
const flight = await Flight.query().find(1);
 
// استرجاع النموذج الأول الذي يطابق قيود الاستعلام...
const flight = await Flight.query().where('active', 1).first();

استرجاع أو إنشاء النماذج

ستحاول طريقة firstOrCreate تحديد موقع سجل قاعدة البيانات باستخدام أزواج العمود / القيمة المعطاة. إذا لم يتم العثور على النموذج في قاعدة البيانات، سيتم إدراج سجل بالقيم الناتجة عن دمج الوسيطة الأولى مع الوسيطة الثانية الاختيارية:

ستحاول طريقة firstOrNew، مثل firstOrCreate، تحديد موقع سجل في قاعدة البيانات يطابق السمات المعطاة. ومع ذلك، إذا لم يتم العثور على نموذج، سيتم إرجاع مثيل نموذج جديد. لاحظ أن النموذج الذي يتم إرجاعه بواسطة firstOrNew لم يتم حفظه بعد في قاعدة البيانات. ستحتاج إلى استدعاء طريقة save يدويًا لحفظه:

const { Flight } = require('./modles');
 
// استرجاع الرحلة بالاسم أو إنشاؤها إذا لم تكن موجودة...
const flight = await Flight.query().firstOrCreate({
  name: 'London to Paris'
});
 
// استرجاع الرحلة بالاسم أو إنشاؤها بالاسم، مؤجلة، ووقت الوصول...
const flight = await Flight.query().firstOrCreate(
  { name: 'London to Paris' },
  { delayed: 1, arrival_time: '11:30' }
);
 
// استرجاع الرحلة بالاسم أو إنشاء مثيل جديد للرحلة...
const flight = await Flight.query().firstOrNew({
  name: 'London to Paris'
});
 
// استرجاع الرحلة بالاسم أو إنشاء مثيل بالاسم، مؤجلة، ووقت الوصول...
const flight = await Flight.query().firstOrNew(
  { name: 'Tokyo to Sydney' },
  { delayed: 1, arrival_time: '11:30' }
);

استرجاع المجاميع

عند التفاعل مع نماذج Sutando، يمكنك أيضًا استخدام طرق count، sum، max، وطرق المجاميع الأخرى التي يوفرها منشئ الاستعلامات. كما قد تتوقع، تعيد هذه الطرق قيمة قياسية بدلاً من مثيل نموذج Sutando:

const count = await Flight.query().where('active', 1).count(); // 100
 
const max = await Flight.query().where('active', 1).max('price'); // 104
 
const flight = await Flight.query().find(1); // flight instanceof Flight

إدراج وتحديث النماذج

الإدراجات

بالطبع، عند استخدام Sutando، لا نحتاج فقط إلى استرجاع النماذج من قاعدة البيانات. نحتاج أيضًا إلى إدراج سجلات جديدة. لحسن الحظ، يجعل Sutando الأمر بسيطًا. لإدراج سجل جديد في قاعدة البيانات، يجب عليك إنشاء مثيل نموذج جديد وتعيين السمات على النموذج. ثم، استدعاء طريقة save على مثيل النموذج:

const { Flight } = require('./model');
 
  const flight = new Flight;
  flight.name = req.name;
  await flight.save();
 
  res.send(flight);

في هذا المثال، نقوم بتعيين حقل الاسم من الطلب HTTP الوارد إلى سمة name لمثيل نموذج Flight. عندما نستدعي طريقة save، سيتم إدراج سجل في قاعدة البيانات. سيتم تعيين الطوابع الزمنية created_at و updated_at للنموذج تلقائيًا عند استدعاء طريقة save، لذلك لا حاجة لتعيينها يدويًا.

بدلاً من ذلك، يمكنك استخدام طريقة create “لحفظ” نموذج جديد باستخدام بيان PHP واحد. سيتم إرجاع مثيل النموذج المدرج إليك بواسطة طريقة create:

const { Flight } = require('./model');
 
const flight = await Flight.query().create({
  name: 'London to Paris',
});

التحديثات

يمكن أيضًا استخدام طريقة save لتحديث النماذج الموجودة بالفعل في قاعدة البيانات. لتحديث نموذج، يجب عليك استرجاعه وتعيين أي سمات ترغب في تحديثها. ثم، يجب عليك استدعاء طريقة حفظ النموذج. مرة أخرى، سيتم تحديث الطابع الزمني updated_at تلقائيًا، لذلك لا حاجة لتعيين قيمته يدويًا:

const { Flight } = require('./model');
 
const flight = await Flight.query().find(1);
flight.name = 'Paris to London';
await flight.save();

التحديثات الجماعية

يمكن أيضًا إجراء التحديثات ضد النماذج التي تطابق استعلامًا معينًا. في هذا المثال، سيتم وضع علامة على جميع الرحلات التي تكون active ولديها destination في San Diego على أنها مؤجلة:

await Flight.query().where('active', 1)
  .where('destination', 'San Diego')
  .update({
    delayed: 1,
  });

تتوقع طريقة update مصفوفة من أزواج العمود والقيمة التي تمثل الأعمدة التي يجب تحديثها. تعيد طريقة update عدد الصفوف المتأثرة.

:::tip عند إصدار تحديث جماعي، لن يتم إطلاق أحداث النموذج saving، saved، updating، و updated للنماذج المحدثة. هذا لأن النماذج لا يتم استرجاعها فعليًا عند إصدار تحديث جماعي. :::

فحص تغييرات السمات

يوفر Sutando طرق isDirty لفحص الحالة الداخلية لنموذجك وتحديد كيفية تغير سماته منذ استرجاع النموذج في الأصل.

تحدد طريقة isDirty ما إذا كانت أي من سمات النموذج قد تغيرت منذ استرجاع النموذج. يمكنك تمرير اسم سمة محددة أو مصفوفة من السمات إلى طريقة isDirty لتحديد ما إذا كانت أي من السمات “متسخة”. تقبل هذه الطريقة أيضًا وسيطة سمة اختيارية:

const { Flight } = require('./model');
 
const user = await User.query().create({
  first_name: 'Taylor',
  last_name: 'Otwell',
  title: 'Developer',
});
 
user.title = 'Painter';
 
user.isDirty(); // true
user.isDirty('title'); // true
user.isDirty('first_name'); // false
user.isDirty(['first_name', 'title']); // true
 
await user.save();
 
user.isDirty(); // false

التحديثات أو الإدراجات

في بعض الأحيان، قد تحتاج إلى تحديث نموذج موجود أو إنشاء نموذج جديد إذا لم يكن هناك نموذج مطابق. مثل طريقة firstOrCreate، تقوم طريقة updateOrCreate بحفظ النموذج، لذلك لا حاجة لاستدعاء طريقة save يدويًا.

في المثال أدناه، إذا كانت هناك رحلة موجودة بموقع مغادرة أوكلاند ووجهة سان دييغو، فسيتم تحديث أعمدة price و discounted. إذا لم تكن هناك رحلة كهذه، سيتم إنشاء رحلة جديدة تحتوي على السمات الناتجة عن دمج كائن الوسيطة الأولى مع كائن الوسيطة الثانية:

const flight = await Flight.query().updateOrCreate(
  {
    departure: 'Oakland',
    destination: 'San Diego'
  },
  {
    price: 99,
    discounted: 1
  }
);

حذف النماذج

لحذف نموذج، يمكنك استدعاء طريقة delete على مثيل النموذج:

const { Flight } = require('./models');
 
const flight = await Flight.query().find(1);
await flight.delete();

حذف نموذج موجود بواسطة مفتاحه الأساسي

في المثال أعلاه، نقوم باسترجاع النموذج من قاعدة البيانات قبل استدعاء طريقة delete. ومع ذلك، إذا كنت تعرف المفتاح الأساسي للنموذج، يمكنك حذف النموذج دون استرجاعه صراحةً عن طريق استدعاء طريقة destroy. بالإضافة إلى قبول مفتاح أساسي واحد، ستقبل طريقة destroy مفاتيح أساسية متعددة، أو مصفوفة من المفاتيح الأساسية، أو مجموعة Collection من المفاتيح الأساسية:

await Flight.query().destroy(1);
 
await Flight.query().destroy(1, 2, 3);
 
await Flight.query().destroy([1, 2, 3]);

حذف النماذج باستخدام الاستعلامات

بالطبع، يمكنك بناء استعلام Sutando لحذف جميع النماذج التي تطابق معايير استعلامك. في هذا المثال، سنحذف جميع الرحلات التي تم وضع علامة عليها على أنها غير نشطة. مثل التحديثات الجماعية، لن تطلق عمليات الحذف الجماعي أحداث النموذج للنماذج المحذوفة:

const deleted = await Flight.query().where('active', 0).delete();

الحذف الناعم

بالإضافة إلى إزالة السجلات فعليًا من قاعدة البيانات الخاصة بك، يمكن لـ Sutando أيضًا “حذف النماذج بشكل ناعم”. عندما يتم حذف النماذج بشكل ناعم، لا يتم إزالتها فعليًا من قاعدة البيانات الخاصة بك. بدلاً من ذلك، يتم تعيين سمة deleted_at على النموذج تشير إلى التاريخ والوقت الذي تم فيه “حذف” النموذج. لتمكين الحذف الناعم لنموذج، استخدم المكون الإضافي SoftDeletes وأضف حقل deleted_at في جدول البيانات المقابل:

const { Model, compose, SoftDeletes } from "kawkab";
 
class Flight extends compose(Model, SoftDeletes) {
  // ...
}

الآن، عند استدعاء طريقة delete على النموذج، سيتم تعيين عمود deleted_at على التاريخ والوقت الحاليين. ومع ذلك، سيتم ترك سجل قاعدة البيانات للنموذج في الجدول. عند استعلام نموذج يستخدم الحذف الناعم، سيتم استبعاد النماذج المحذوفة بشكل ناعم تلقائيًا من جميع نتائج الاستعلام.

لتحديد ما إذا كان مثيل النموذج المعطى قد تم حذفه بشكل ناعم، يمكنك استخدام طريقة trashed:

if (flight.trashed()) {
  //
}

استعادة النماذج المحذوفة بشكل ناعم

في بعض الأحيان قد ترغب في “إلغاء حذف” نموذج محذوف بشكل ناعم. لاستعادة نموذج محذوف بشكل ناعم، يمكنك استدعاء طريقة restore على مثيل النموذج. ستقوم طريقة restore بتعيين عمود deleted_at للنموذج إلى null:

await flight.restore();

يمكنك أيضًا استخدام طريقة restore في استعلام لاستعادة نماذج متعددة. مرة أخرى، مثل العمليات “الجماعية” الأخرى، لن تطلق هذه العملية أي أحداث نموذج للنماذج التي تم استعادتها:

await Flight.query().withTrashed()
  .where('airline_id', 1)
  .restore();

يمكن أيضًا استخدام طريقة restore عند بناء استعلامات العلاقات:

await flight.related('history').restore();

حذف النماذج بشكل دائم

في بعض الأحيان قد تحتاج إلى إزالة نموذج فعليًا من قاعدة البيانات الخاصة بك. يمكنك استخدام طريقة forceDelete لإزالة نموذج بشكل دائم من جدول قاعدة البيانات:

await flight.forceDelete();

يمكنك أيضًا استخدام طريقة forceDelete عند بناء استعلامات العلاقات Sutando:

await flight.related('history').forceDelete();

استعلام النماذج المحذوفة بشكل ناعم

تضمين النماذج المحذوفة بشكل ناعم

كما هو مذكور أعلاه، سيتم استبعاد النماذج المحذوفة بشكل ناعم تلقائيًا من نتائج الاستعلام. ومع ذلك، يمكنك إجبار النماذج المحذوفة بشكل ناعم على تضمينها في نتائج الاستعلام عن طريق استدعاء طريقة withTrashed على الاستعلام:

const { Flight } = require('./models');
 
const flights = await Flight.query().withTrashed()
  .where('account_id', 1)
  .get();

يمكن أيضًا استدعاء طريقة withTrashed عند بناء استعلام علاقة:

await flight.related('history').withTrashed().get();

استرجاع النماذج المحذوفة بشكل ناعم فقط

ستسترجع طريقة onlyTrashed النماذج المحذوفة بشكل ناعم فقط:

const flights = await Flight.query().onlyTrashed()
  .where('airline_id', 1)
  .get();

نطاقات الاستعلام

تتيح لك النطاقات تعريف مجموعات شائعة من قيود الاستعلام التي يمكنك إعادة استخدامها بسهولة في جميع أنحاء تطبيقك. على سبيل المثال، قد تحتاج إلى استرجاع جميع المستخدمين الذين يعتبرون “شعبية” بشكل متكرر. لتعريف نطاق، قم بإضافة الطريقة إلى نموذج Sutando مع بادئة scope.

يجب أن تعيد النطاقات دائمًا نفس مثيل منشئ الاستعلام أو void:

const { Model } = require('./models');
 
class User extends Model {
  scopePopular(query){
    return query.where('votes', '>', 100);
  }
 
  scopeActive(query){
    query.where('active', 1);
  }
}

استخدام النطاق

بمجرد تعريف النطاق، يمكنك استدعاء طرق النطاق عند استعلام النموذج. ومع ذلك، يجب عدم تضمين بادئة scope عند استدعاء الطريقة. يمكنك حتى ربط استدعاءات لنطاقات مختلفة:

const { User } = require('./models');
 
const users = await User.query().popular().active().orderBy('created_at').get();

قد يتطلب دمج نطاقات نموذج Sutando متعددة عبر عامل استعلام or استخدام الإغلاق لتحقيق التجميع المنطقي الصحيح:

const users = await User.query().popular().orWhere(query => {
  query.active();
}).get();

النطاقات الديناميكية

في بعض الأحيان قد ترغب في تعريف نطاق يقبل معلمات. للبدء، قم بإضافة المعلمات الإضافية إلى توقيع طريقة النطاق الخاصة بك. يجب تعريف معلمات النطاق بعد معلمة query:

const { Model } = require('./models');
 
class User extends Model {
  scopeOfType(query, type){
    return query.where('type', type);
  }
}

بمجرد إضافة الوسائط المتوقعة إلى توقيع طريقة النطاق الخاصة بك، يمكنك تمرير الوسائط عند استدعاء النطاق:

const users = await User.query().ofType('admin').get();

مقارنة النماذج

في بعض الأحيان قد تحتاج إلى تحديد ما إذا كانت نموذجين “نفس” أو لا. يمكن استخدام طرق is و isNot للتحقق بسرعة مما إذا كانت النموذجين لهما نفس المفتاح الأساسي والجدول واتصال قاعدة البيانات أو لا:

if (post.is(anotherPost)) {
  //
}
 
if (post.isNot(anotherPost)) {
  //
}