سنتعرّف في سلسلة الدروس هذه والتي تبدأ بهذا الدرس على ارتباطات Active Record في روبي أند ريلز وسنبدأ أولًا بمعرفة لماذا الارتباطات:
1 لماذا الارتباطات Associations؟
في Rails، الارتباط association هو اتصال بين نموذجي Active Record. لماذا نحتاج إلى هذه الارتباطات بين النماذج؟ وذلك لأن العمليات المشتركة ستصبح أبسط وأسهل في الشيفرة البرمجية، على سبيل المثال، اعتبر أن تطبيق Rails بسيط يتضمن نموذج للمؤلفين ونموذج للكتب، كل مؤلف قد يملك العديد من الكتب، وبدون الارتباطات، سيشبه إعلان النموذج هذا:
class Author < ApplicationRecord end class Book < ApplicationRecord end
الآن، لنفترض أننا نريد إضافة كتاب جديد للمؤلف الحالي:
@book = Book.create(published_at: Time.now, author_id: @author.id)
أو حذف المؤلف مع جميع كتبه:
@books = Book.where(author_id: @author.id)
@books.each do |book|
book.destroy
end
@author.destroy
باستخدام ارتباطات Active Record، يمكننا تبسيط هذه العملية (وغيرها) عن طريق إعلان لـ Rails نخبره بوجود اتصال بين النموذجين، وهذه الشيفرة البرمجية معدلة لإعداد المؤلفين والكتب:
class Author < ApplicationRecord
has_many :books, dependent: :destroy
end
class Book < ApplicationRecord
belongs_to :author
end
وبهذا، ستصبح عملية إنشاء كتاب لمؤلف معين أسهل:
@book = @author.books.create(published_at: Time.now)
بالإضافة إلى أن عملية حذف المؤلف مع جميع كتبه أسهل بكثير:
@author.destroy
لمزيد من المعلومات حول الأنواع المختلفة للارتباطات، اقرأ القسم التالي من هذا الدليل، وستجد بعدها بعض النصائح والحيل للعمل مع الارتباطات، ومن ثم مرجع كامل للنماذج وخيارات الارتباطات في Rails.
2 أنواع الارتباطات Associations
يدعم Rails ستة أنواع من الارتباطات Associations:
-
belongs_to
-
has_one
-
has_many
-
has_many :through
-
has_one :through
-
has_and_belongs_to_many
تنفّذ الارتباطات Association باستخدام نداءات نمط ماكرو (macro-style) بحيث يمكنك إضافة ميزات إلى نماذج، فعلى سبيل المثال، من خلال إعلان أن نموذج ينتمي belongs_to
إلى آخر، ستجعل Rails يحافظ على بيانات المفتاح الرئيسي والمفتاح الخارجي (Primary Key-Foreign Key) بين مثيلات النموذجين، ويمكنك الحصول على عدد الأساليب المضافة إلى نموذجك.
في ما تبقى من هذا الدليل، ستتعلم كيف تُعلن وتستخدم أشكال مختلفة من الارتباطات، لكن أولا، مقدمة قصيرة إلى الحالات التي تتناسب معها كل نوع من الارتباطات.
2.1 ارتباط belongs_to
يعيّن ارتباط belongs_to
اتصال واحد لواحد (one-to-one) مع نموذج آخر، بحيث أن كل مثيل للنموذج المعلن “يرتبط” بمثيل واحد للنموذج الآخر، فعلى سبيل المثال، إذا كان التطبيق يتضمن مؤلفين وكتب وكل كتاب يرتبط فقط إلى مؤلف واحد، يمكنك إعلان نموذج الكتاب كالتالي:
class Book < ApplicationRecord
belongs_to :author
end
Quoteملاحظة: يجب أن تستخدم ارتباطات belongs_to صيغة المفرد، فإذا استخدمت صياغة الجمع في المثال أعلاه لارتباط المؤلف مع نموذج الكتاب، ستظهر لك رسالة "
uninitialized constant Book::Authors
" وذلك لأن Rails يستنتج اسم الصنف من اسم الارتباط، إذا جُمع اسم الارتباط عن طريق الخطأ فالأصناف المستنسخة ستجمع عن طريق الخطأ.
عملية التهجير ستشبه هذه:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :authors do |t|
t.string :name
t.timestamps
end
create_table :books do |t|
t.belongs_to :author, index: true
t.datetime :published_at
t.timestamps
end
end
end
2.2 ارتباط has_one
يعيّن ارتباط has_one
أيضا اتصال واحد لواحد (one-to-one) مع نموذج آخر، لكن مع دلالات (semantics) -وعواقب- مختلفة، فيشير هذا الارتباط أن كل مثيل للنموذج يحتوي أو يمتلك مثيل لنموذج آخر فعلى سبيل المثال، إذا يملك كل مُورّد في تطبيقك حساب واحد فقط، سيشبه إعلان نموذج المورد هذا:
class Supplier < ApplicationRecord
has_one :account
end
عملية التهجير ستشبه هذه:
class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps
end
end
end
بناءََ على حالة الاستخدام، ستحتاج إلى إنشاء فهرس فريد و/أو إلى قيد مفتاح خارجي (foreign key
) على عمود المُورّد لجدول الحسابات، في هذه الحالة، سيشبه تعريف العمود لهذا:
create_table :accounts do |t|
t.belongs_to :supplier, index: { unique: true }, foreign_key: true
# ...
end
2.3 ارتباط has_many
يشير ارتباط has_many
إلى اتصال واحد إلى الكثير (one-to-many) مع نموذج آخر، ستجد في الكثير من الأحيان هذا الارتباط في الجانب الآخر لارتباط belongs_to
، وهذا الارتباط يشير إلى أن كل مثيل للنموذج يملك صفر مثيل أو أكثر للنموذج الآخر، فعلى سبيل المثال، في التطبيق الذي يحتوي على المؤلفين وكتب، يمكنك الإعلان عن نموذج المؤلف كالتالي:
class Author < ApplicationRecord
has_many :books
end
Quoteملاحظة: يُجمع – صياغة الجمع - اسم النموذج الآخر عند إعلان ارتباط has_many.
عملية التهجير ستشبه هذه:
class CreateAuthors < ActiveRecord::Migration[5.0]
def change
create_table :authors do |t|
t.string :name
t.timestamps
end
create_table :books do |t|
t.belongs_to :author, index: true
t.datetime :published_at
t.timestamps
end
end
end
2.4 ارتباط has_many :through
يُستخدم ارتباط has_many :through
لإنشاء اتصال كثير إلى كثير(many-to-many) مع نموذج آخر، فهذا النموذج يشير إلى أن النموذج المُعلن يمكن أن يقابل صفر مثيل أو أكثر من نموذج آخر من خلال نموذج ثالث، فعلى سبيل المثال، فكر في العمل الطبي حيث يحدد المرضى مواعيد لرؤية الأطباء، فسيُعلن الارتباط كالتالي:
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
عملية التهجير ستشبه هذه:
class CreateAppointments < ActiveRecord::Migration[5.0]
def change
create_table :physicians do |t|
t.string :name
t.timestamps
end
create_table :patients do |t|
t.string :name
t.timestamps
end
create_table :appointments do |t|
t.belongs_to :physician, index: true
t.belongs_to :patient, index: true
t.datetime :appointment_date
t.timestamps
end
end
end
يمكنك إدارة مجموعة نماذج المنضمّة عن طريق أساليب ارتباط has_many
، فعلى سبيل المثال، إذا عيّنت هذا:
physician.patients = patients
فإنه سينشئ النماذج المنضمّة تلقائيا للكائنات المرتبطة حديثا، إذا كان بعضها موجود سابقا وفُقِد حاليا، فستُحذف صفوف الضم الخاصة به تلقائيا.
Quoteتنبيه: تحذف النماذج المنضمّة تلقائيا بشكل مباشر، فلن تشتغل دالة الاستدعاء للتدمير.
يمكنك الاستفادة من ارتباط has_many :through
لإنشاء "اختصارات” من خلال ارتباطات has_many
متداخلة، فعلى سبيل المثال، إذا كان مستند يملك أقسام عديدة، ويحتوي القسم على فقرات، فقد تحتاج في بعض الأحيان إلى الحصول على مجموعة بسيطة من جميع الفقرات في المستند، ويمكنك إنشاء ذلك عن طريق:
class Document < ApplicationRecord
has_many :sections
has_many :paragraphs, through: :sections
end
class Section < ApplicationRecord
belongs_to :document
has_many :paragraphs
end
class Paragraph < ApplicationRecord
belongs_to :section
end
سيفهم Rails هذا الآن عن طريق محدد through: :sections
:
@document.paragraphs
2.5 ارتباط has_one :through
تنشئ ارتباطات has_one :through
اتصال واحد لواحد (one-to-one) مع نموذج آخر، هذا الارتباط يشير إلى أن النموذج المُعلن عنه يمكن أن يقابل مع مثيل من نموذج آخر من خلال نموذج آخر، على سبيل المثال، إذا كان يملك كل مُورّد حساب واحد، وكل حساب مرتبط بسجل حساب واحد، فسيكون نموذج المُورّد كالتالي:
class Supplier < ApplicationRecord
has_one :account
has_one :account_history, through: :account
end
class Account < ApplicationRecord
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ApplicationRecord
belongs_to :account
end
عملية التهجير ستشبه هذه:
class CreateAccountHistories < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps
end
create_table :account_histories do |t|
t.belongs_to :account, index: true
t.integer :credit_rating
t.timestamps
end
end
end
2.6 الارتباط has_and_belongs_to_many
ينشئ ارتباط has_and_belongs_to_many
اتصال الكثير إلى الكثير many-to-many مباشر مع نموذج آخر بدون تدخل نموذج ثالث، فعلى سبيل المثال، إذا كان تطبيقك يحتوي على تجميعات وأجزاء، ستجد أجزاء عديدة مع كل تجميع وكل جزء يظهر في الكثير من التجميعات، فيمكنك إعلان هذا النموذج كالتالي:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
عملية التهجير ستشبه هذه:
class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
def change
create_table :assemblies do |t|
t.string :name
t.timestamps
end
create_table :parts do |t|
t.string :part_number
t.timestamps
end
create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly, index: true
t.belongs_to :part, index: true
end
end
end
2.7 الاختيار بين belongs_to
و has_one
إذا أردت إنشاء علاقة واحد إلى واحد (one-to-one) بين نموذجين، فستحتاج إلى إضافة belongs_to to
إلى أحدهم و has_one إلى الآخر، فكيف ستعرف لمن تضيف هذا؟
يمكنك تمييز ذلك عن طريق المكان الذي وضعت فيه المفتاح الخارجي (ستكون في الجدول الصنف الذي أعلنت فيه ارتباط belongs_to
)، لكن يجب عليك أن تفكر في معنى الفعلي للبيانات، فعلاقة has_one
تعني أنه يوجد شيء خاص بك، أي أن جزء منه يعود لك، فعلى سبيل المثال، من المنطقي القول بأن المُورّد يملك حساب على أن الحساب يملك مُورّد، وهذا يعني أن العلاقة الصحيحة مشابهة لهذه:
class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
end
عملية التهجير ستشبه هذه:
class CreateSuppliers < ActiveRecord::Migration[5.0]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.integer :supplier_id
t.string :account_number
t.timestamps
end
add_index :accounts, :supplier_id
end
end
Quoteملاحظة: يمكنك جعل اسم المفتاح الخارجي واضح وصريح عن طريق استخدام
t.integer :supplier_id
، في الإصدار Rails الحالي، يمكنك استخدامt.references :supplier
بدلاً من ذلك.
2.8 الاختيار بين has_many :through
و has_and_belongs_to_many
يوفر Rails طريقتين لإعلان علاقة الكثير إلى الكثير (many-to-many) بين النماذج، أبسط طريقة هي عن طريق استخدام has_and_belongs_to_many
، والتي تمكنك من إنشاء هذا الارتباط مباشرة:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
الطريقة الثانية هي عن طريق استخدام has_many :through
لجعل الارتباط غير مباشرة، عن طريق ضم نموذج:
class Assembly < ApplicationRecord
has_many :manifests
has_many :parts, through: :manifests
end
class Manifest < ApplicationRecord
belongs_to :assembly
belongs_to :part
end
class Part < ApplicationRecord
has_many :manifests
has_many :assemblies, through: :manifests
end
ببساطة، إذا أردت العمل مع نموذج العلاقة ككيان مستقل، يجب عليك إنشاء علاقة has_many :through
، وإذا لم تحتاج إلى القيام بأي شيء مع نموذج العلاقة، سيكون من الأسهل إنشاء علاقة has_and_belongs_to_many
(لا تنسى إنشاء جدول الضم joining table في قاعدة البيانات).
يجب عليك استخدام has_many :through
إذا احتجت إلى عمليات تحقق validations، دوال استدعاء callbacks أو سمات attributes إضافيَة في نموذج الضم.
2.9 ارتباطات متعددة الأشكال Polymorphic Associations
من المواضيع المتقدمة قليلا في الارتباطات، هي الارتباطات متعددة الأشكال، فعن طريقها، يمكن للنموذج أن يرتبط بأكثر من نموذج بارتباط واحد، فعلى سبيل المثال، قد تملك نموذج صورة التي تنتمي إلى نموذج الموظف أو إلى نموذج المنتج، ويمكنك الإعلان عن ذلك عن طريق التالي:
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
فكر في إعلان belongs_to
متعدد الأشكال على أنه إنشاء لواجهة يمكن لأي نموذج آخر أن يستخدمها، من مثيل نموذج Employee، يمكنك استرداد مجموع من الصور عن طريق:
@employee.pictures.
وبنفس الطريقة يمكنك استرداد product.pictures@
.
إذا كنت تملك مثيل من نموذج الصورة، فيمكنك الحصول على الأب (parent) عن طريق picture.imageable@
، ولتحقيق ذلك، تحتاج إلى إعلان كل من عمود مفتاح خارجي وعمود النوع في النموذج الذي أعلنت فيه واجهة متعددة الأشكال:
class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
t.timestamps
end
add_index :pictures, [:imageable_type, :imageable_id]
end
end
يمكن تبسيط عملية التهجير عن طريق استخدام t.references
:
class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true, index: true
t.timestamps
end
end
end
2.10 ضم ذاتي Self Joins
عند تصميم نموذج بيانات، ستجد في بعض الأحيان أن العلاقة يجب أن تكون مرتبطة بنفسها، فعلى سبيل المثال، قد تريد تخزين جميع الموظفين في نموذج قاعدة بيانات واحدة، لكن يجب تقدر أيضا على تتبع العلاقات مثل علاقة المدير بالمرؤوسين، وهذه الحالة يمكن إنشاءها عن طريق ارتباطات الضم الذاتي:
class Employee < ApplicationRecord
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
belongs_to :manager, class_name: "Employee"
end
مع هذا الإعداد، يمكنك استرداد employee.subordinates@
و employee.manager@
.
في عمليات التهجير/المخطط، ستحتاج إلى إضافة عمود المرجع إلى نفس النموذج.
class CreateEmployees < ActiveRecord::Migration[5.0]
def change
create_table :employees do |t|
t.references :manager, index: true
t.timestamps
end
end
end
وسنتابع التعرّف على الإرتباطات في Active Record في الدروس اللاحقة.
المصدر:
توثيقات Ruby on Rails
تم التعديل في بواسطة zahershullar
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.