تهجيرات السجل الفعال في ريلز
التهجيرات (Migrations) هي ميزة من السجل الفعال التي تمكّنك من تحديث مخطط قاعدة البيانات على مر الوقت. بدلًا من كتابة تعديلات قاعدة البيانات باستخدام SQL، تمكّنك التهجيرات من كتابة تعليمات DSL باستخدام روبي لتحديث جداولك. بعد قراءة هذا الدليل، ستتعرَّف على:
- المولِّدات التي تستطيع استعمالها لإنشاء التهجيرات.
- التوابع التي يوفرها السجل الفعَّال لتعديل قاعدة البيانات.
- المهام bin/rails التي تعدِّل وتتحكم بالتهجيرات والمخطط (schema) الخاص بك.
- العلاقة بين التهجيرات والملف schema.rb.
نظرة عامة على التهجيرات
إن التهجيرات هي طريقة مناسبة وسلسة من أجل تحديث مخطط قاعدة البيانات على مر الوقت بطريقة متناسقة وسهلة. تستخدم التهجيرات تعليمات DSL باستخدام روبي، مما يعني أنه ليس عليك كتابة تعليمات SQL يدويًا، الأمر الذي يؤدي إلى جعل تهجيراتك مستقلة.
يمكنك اعتبار التهجيرات كإصدارات مختلفة من قاعدة البيانات؛ إذ يبدأ المخطط بلا شيء، وكل تهجير يضيف أو يحذف الجداول أو الأعمدة أو السجلات. يعرف السجل الفعال متى وكيف يتوجب تحديث قاعدة البيانات في هذا المخطط الزمني، مما يمكّن نقل حالة قاعدة البيانات من أي نقطة لآخر إصدار ممكن. يحدِّث السجل الفعال أيضًا الملف db/schema.rb لمطابقة البنية الأخيرة لقاعدة البيانات الخاصة بك.
إليك مثال بسيط عن التهجيرات:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
يضيف هذا التهجير جدولًا يسمى products
، يحتوي عمودًا من النوع string
يسمى name
، وعمودًا آخر من نوع النصي (text) يسمى description
. يتم أيضًا إضافة مفتاح رئيسي يسمى id
بشكل ضمني، لكونه المفتاح الرئيسي الافتراضي لكل نماذج السجل الفعال. يضيف الماكرو timestamps
عمودين هما: created_at
و updated_at
. يتم إدارة هذين العمودين تلقائيًا من قبل السجل الفعال في حال وجودهما.
من الجدير بالذكر أننا قمنا بالتعريف عن التغيير الذي نريد وجوده بعد وقت. قبل تنفيذ هذا التهجير، لن يكون هناك جدول. وبعد تنفيذه، سيوجد الجدول. يعرف السجل الفعال كيف تتم إعادة التهجير أيضًا: إذا أعدنا هذا التهجير، سيتم إزالة الجدول.
في قواعد البيانات التي تدعم عمليات النقل (transactions) مع التعليمات التي تحدث تغييرًا في مخطط قاعدة البيانات، يتم تغليف التهجيرات في عملية نقل. في حال كانت قاعدة البيانات لا تدعم عمليات النقل هذه، وعند فشل تنفيذ تهجير ما، لن يتم إعادة الأجزاء التي نجحت منه. إذ من الضروري إعادتها يدويًا.
ملاحظة: هناك بعض الاستعلامات (queries) التي لا يمكن تنفيذها ضمن عملية نقل. في حال كان محول قاعدة البيانات يدعم عمليات نقل DDL، يمكنك استخدام disable_ddl_transaction!
لإلغاء تفعيلها من أجل تهجير وحيد.
في حال أردت إحداث تغيير في التهجير لا يمكن للسجل الفعال معرفة كيفية إعادته، يمكنك استخدام reversible
:
class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def change
reversible do |dir|
change_table :products do |t|
dir.up { t.change :price, :string }
dir.down { t.change :price, :integer }
end
end
end
end
وبشكل بديل، يمكنك استخدام التوابع up
و down
بدلًا من change
:
class ChangeProductsPrice < ActiveRecord::Migration[5.0]
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
إنشاء تهجير
إنشاء تهجير وحيد (Standalone)
تخزن التهجيرات في ملفات ضمن المجلد db/migrate، واحد لكل صنف تهجير. يسمى ملف التهجير وفق النمط YYYYMMDDHHMMSS_create_products.rb
، الذي يوافق توقيت غرينتش الذي عُرِّف في أثنائه التجيره يليه اسم التهجير مع الفصل بينهما (وبين الكلمات) بشرطة سفلية. يجب على اسم صنف التهجير (المسمى بطريقة سنام الجمل [CamelCase]) أن يوافق اسم ملف التهجير. مثلًا، الملف 20080906120000_create_products.rb يجب أن يعرِّف الصنف CreateProducts
، والملف 20080906120001_add_details_to_products.rb يجب أن يعرِّف الصنف AddDetailsToProducts
. يستخدم ريلز هذه التوقيتات الزمنية لمعرفة التهجيرات التي يجب تنفيذها والترتيب المراد تنفيذها به؛ لذا، في حال نسخ تهجير من تطبيق آخر، أو عند توليد تهجيرات جديدة، يجب الانتباه إلى ترتيب هذه التهجيرات.
بالطبع، ليس من السهل توليد وحساب التوقيتات الزمنية، لذلك يزودنا السجل الفعال بمولد ينشئها لنا بسهولة:
$ bin/rails generate migration AddPartNumberToProducts
سينشئ هذا تهجيرًا فارغًا:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
end
end
في حال كان اسم التهجير بالشكل "AddXXXToYYY" أو "RemoveXXXFromYYY" واتبِع بقائمة بالأعمدة وأنواعها، فسيتم إنشاء تهجير يحتوي على التعابير add_column
و remove_column
بطريقة مناسبة. أي:
$ bin/rails generate migration AddPartNumberToProducts part_number:string
سيولّد:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
end
end
وفي حال أردت إضافة فهرس للعمود الجديد، يمكنك ذلك من خلال:
$ bin/rails generate migration AddPartNumberToProducts part_number:string:index
سيولّد:
class AddPartNumberToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
وبطريقة مماثلة، يمكنك توليد تهجير يؤدي إلى حذف عمود وذلك كالتالي:
$ bin/rails generate migration RemovePartNumberFromProducts part_number:string
يولّد:
class RemovePartNumberFromProducts < ActiveRecord::Migration[5.0]
def change
remove_column :products, :part_number, :string
end
end
لست مجبرًا على عمود وحيد فقط. إليك مثلًا:
$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
يولّد:
class AddDetailsToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
في حال كان اسم التهجير بالشكل "CreateXXX"، وتبعه قائمةٌ بالأعمدة وأنواعها، فسيتم إنشاء تهجير ينشئ جدولًا يسمى XXX يحتوي على الأعمدة المحددة. مثلًا:
$ bin/rails generate migration CreateProducts name:string part_number:string
يولّد:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.string :part_number
end
end
end
وكما جرت العادة، ما تم توليده هو نقطة البداية فقط. يمكنك الإضافة أو الحذف من الملف المولد كما تريد عن طريق تعديل الملف db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb.
أيضاً، يقبل المولّد أنواع الأعمدة كموارد مثل references
(أو belongs_to
متاح أيضًا). مثلًا:
$ bin/rails generate migration AddUserRefToProducts user:references
يولّد:
class AddUserRefToProducts < ActiveRecord::Migration[5.0]
def change
add_reference :products, :user, foreign_key: true
end
end
ينشئ هذا التهجير العمود user_id
وفهرس مناسب. للاطلاع على قائمة خيارات add_reference الكاملة، انتقل إلى توثيق الواجهة البرمجية.
يوجد أيضًا مولّد لإنشاء جداول ربط (join tables) في حال كانت الكلمة JoinTable
جزءًا من اسم التهجير:
$ bin/rails g migration CreateJoinTableCustomerProduct customer product
يولّد:
class CreateJoinTableCustomerProduct < ActiveRecord::Migration[5.0]
def change
create_join_table :customers, :products do |t|
# t.index [:customer_id, :product_id]
# t.index [:product_id, :customer_id]
end
end
end
مولّدات النماذج
تمكّنك مولّدات النماذج (Model Generators) من إنشاء التهجيرات المناسبة للنماذج الجديدة، إذ تحوي التهجيرات على التعليمات المناسبة لإنشاء الجدول المطابق. في حال أخبرت ريلز بالأعمدة التي تريدها، سيتم كتابة تعليمات إنشاء هذه الأعمدة تلقائيًا. إليك مثلًا:
$ bin/rails generate model Product name:string description:text
سيؤدي إلى إنشاء التهجير التالي:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
يمكنك أيضًا إضافة المزيد من الأعمدة وأنواعها في حال أردت ذلك.
تمرير معدلات الأنواع
يمكن تمرير بعض معدلات الأنواع المستخدمة بشكل دوري مباشرةً في سطر الأوامر. توضع معدلات الأنواع ضمن أقواس معقوصة وتلي نوع الحقل. مثلًا، يؤدي تنفيذ الأمر التالي:
$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
إلى إنشاء التهجير التالي:
class AddDetailsToProducts < ActiveRecord::Migration[5.0]
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true
end
end
كتابة التهجيرات
بعد إنشاء تهجيراتك باستخدام أحد المولّدات، فقد حان الوقت للبدء بالعمل به واستخدامه!
إنشاء جدول
إن التابع create_table
هي أحد أهم التوابع لكن في معظم الأوقات، سيتم توليد الجدول تلقائيًا باستخدام أحد المولدات. الاستخدام التقليدي سيكون كالتالي:
create_table :products do |t|
t.string :name
end
الذي ينشئ الجدول products
الذي يحوي عمودًا باسم name
(وكما تم مناقشته أدناه، العمود id
منشأ بشكل ضمني).
افتراضيًا، سينشئ التابع create_table
مفتاحًا رئيسيًا يدعى id
. يمكنك تغيير اسم المفتاح الرئيسي باستخدام الخيار :primary_key
(لا تنسَ تعديل النموذج الموافق)، أو في حال لم ترد إنشاء مفتاح رئيسي إطلاقًا، يمكنك تمرير الخيار id: false
. في حال أردت تمرير خيارات خاصة بقاعدة البيانات، يمكنك وضع قطع SQL في الخيار :option
. مثلًا:
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
سيؤدي إلى إضافة ENGINE=BLACKHOLE
إلى تعليمة SQL المستخدمة لإنشاء الجدول.
أيضًا، يمكنك تمرير الخيار :comment
مع أي شرح أو تعليق حول الجدول الذي سيتم تخزينه في قاعدة البيانات والذي يمكن عرضه في أدوات إدارة قواعد البيانات، مثل MySQL Workbench و PgAdmin III. من المستحسن تعريف التعليقات في التهجيرات للتطبيقات التي تملك قواعد بيانات كبيرة، الأمر الذي يساعد المطوّرين من فهم تحليل النظام وتوليد توثيقات. في الوقت الحالي، فقط محوّلات قواعد البيانات MySQL و PostgreSQL تدعم التعليقات.
إنشاء جدول رابط
التابع التهجيري create_join_table
ينشئ الجدول HABTM (اختصار للعبارة has and belongs to many]) الرابط. يكون استخدامه كالتالي:
create_join_table :products, :categories
ينشئ هذا جدولًا يدعى categories_products
بحقلين category_id
و product_id
. في هذه الحقول، يضبط الخيار :null
إلى القيمة false
افتراضيًّا. يمكن تجاوز هذا عن طريق تحديد الخيار :column_options
.
create_join_table :products, :categories, column_options: { null: true }
بشكل افتراضي، يأتي اسم الجدول الرابط من أول وسيطين ممررين للتابع create_join_table
وذلك بترتيب أبجدي. لتعديل اسم الجدول، قم بتعديل الخيار :table_name
:
create_join_table :products, :categories, table_name: :categorization
يقبل التابع create_join_table
أيضًا كتلةً، والتي يمكنك استخدامها لإضافة الفهارس (التي لا يتم إنشاؤها بشكل افتراضي)، أو الحقول الإضافية:
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
تغيير الجداول
بشكل مقارب للتابع create_table
، يمكن استخدام change_table
من أجل تغيير الجداول الموجودة مسبقًا. يمكن استخدامه بشكل مشابه للتابع create_table
، لكن الكائن المزود للكتلة يملك بعض المزايا الإضافية. مثلاً:
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
يزيل هذا التابع الحقول name
و description
، وينشئ حقلاً يدعى part_number
ويضيف فهرسًا عليه. نهايةً، يعيد تسمية الحقل upccode
.
تغيير الحقول
بشكل مشابه للتابع remove_column
و add_column
، يمكنك استخدام التابع change_column
الذي توفره ريلز لتغيير حقل محدَّد:
change_column :products, :part_number, :text
يغيّر هذا التابع الحقل part_number
من الجدول products
ليصبح حقلًا من النوع النصي (text). لاحظ أن التغييرات التي تجرى عبر التابع change_column
لا رجعة فيها (irreversible).
إلى جانب change_column
، يمكن استخدام التابعين change_column_null
و change_column_default
لتغيير القيد "not null" أو القيم الافتراضية من الحقول وذلك بشكل واضح.
change_column_null :products, :name, false
change_column_default :products, :approved, from: true, to: false
يغيّر هذا الحقل name
من الجدول products
ليصبح NOT NULL
، والقيمة الافتراضية له تصبح false
.
ملاحظة: يمكنك أيضًا كتابة التغيير نفسه بالشكل:
change _column_default :products, :approved, false
لكن على عكس المثال السابق، سيجعل هذا التغيير من تهجيرك غير قابل للاستعادة.
معدّلات الحقول
يمكن تطبيق معدلات الحقول (Column modifiers) عند إنشاء أو تغيير حقل ما:
limit
: يحدد الحجم الأعظمي للحقول التي من الأنواع string، أو text، أو binary، أو integer.precision
: يحدد دقة حقول الأعداد العشرية، إذ يمثل عدد الخانات الرقمية الكلية في العدد.scale
: يحدد دقة المنازل العشرية، إذ يمثل عدد الخانات العشرية بعد الفاصلة العشرية.polymorphic
: يضيف الحقلtype
إلى العلاقات من النوعbelongs_to
.null
: يسمح/لا يسمح بإضافة القيمNULL
في الحقل.default
: يسمح بضبط القيمة الافتراضية في الحقل. تجب الملاحظة أنه عند استخدام قيمة ديناميكية (مثل قيمة تاريخ)، سيتم حساب القيمة الافتراضية في المرة الأولى فقط (عند تطبيق التهجير).index
: يضيف فهرسًا للحقل.comment
: يضيف تعليقًا للحقل.
قد تدعم بعض المحولات (adapters) المزيد من الخيارات؛ اطّلع على توثيق الواجهة البرمجية الخاص بالمحول من أجل المزيد من المعلومات.
ملاحظة: لا يمكن تحديد المعدلات null
و default
من سطر الأوامر.
المفاتيح الأجنبية
رغم أنَّ المفاتيح الأجنبية غير مطلوبة، يمكنك إضافتها لضمان السلامة المرجعية (referential integrity).
add_foreign_key :articles, :authors
يضيف هذا الأمر مفتاحًا أجنبيًا جديدًا للحقل author_id
من الجدول articles
؛ إذ يكون الحقل مرجعًا للحقل id
من الجدول authors
. في حال عدم قابلية اشتقاق أسماء الحقول من أسماء الجداول، يمكنك استخدام الخيارين :column
و :primary_key
.
يقوم ريلز بتوليد اسم من أجل كل مفتاح أجنبي يبدأ بالسابقة _fk_rails
، ويليها 10 محارف يتم توليدها من اسم الجدول from_table
والحقل column
. يمكن تحديد اسم المفتاح الأجنبي من الخيار :name
.
ملاحظة: يدعم السجل الفعال المفاتيح الأجنبية الوحيدة فقط. لاستخدام المفاتيح الأجنبية المركبة، يجب استخدام execute
و structure.sql. اقرأ قسم تعديل المخططات في الأسفل.
يمكن أيضًا إزالة مفتاح أجنبي بسهولة:
# دع السجل الفعال يكتشف اسم الحقل
remove_foreign_key :accounts, :branches
# إزالة المفتاح الأجنبي لحقل معين
remove_foreign_key :accounts, column: :owner_id
# إزالة المفتاح الأجنبي باسم معين
remove_foreign_key :accounts, name: :special_fk_name
عند عدم كفاية التوابع المساعدة
في حال كانت التوابع المساعدة التي يزودها السجل الفعال غير كافية، يمكنك استخدام التابع execute
لتنفيذ تعليمات SQL:
Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1")
للمزيد من المعلومات والأمثلة حول التوابع المفردة، يمكنك الاطلاع على توثيق الواجهة البرمجية. بالتحديد، توثيق ActiveRecord::ConnectionAdapters::SchemaStatements (الذي يزود التوابع المتاحة في التوابع change
و up
و down
)، وتوثيق ActiveRecord::ConnectionAdapters::TableDefinition (الذي يزود التوابع المتاحة في الكائن المعاد بواسطة create_table
)، وتوثيق ActiveRecord::ConnectionAdapters::Table (الذي يزود التوابع المتاحة في الكائن المعاد بواسطة change_table
).
استخدام التابع change
إن التابع change
هو الطريقة الأساسية لكتابة التهجيرات؛ إذ يعمل في معظم الحالات، التي يعلم فيها السجل الفعال كيف يستعيد التهجيرات بشكل تلقائي. في الوقت الحالي، يدعم التابع change
فقط تعريفات التهجيرات التالية:
add_column
add_foreign_key
add_index
add_reference
add_timestamps
change_column_default
(يجب أن يُزوَّد بالخيارين :from
و :to
)change_column_null
create_join_table
create_table
disable_extension
drop_join_table
drop_table
(يجب أن يُزوَّد بكتلة)enable_extension
remove_column
(يجب أن يُزوَّد بنوع)remove_foreign_key
يجب أن يُزوَّد بجدولٍ ثانٍ)remove_index
remove_reference
remove_timestamps
rename_column
rename_index
rename_table
إن التابع change_table
أيضًا قابل للاستعادة (reversible)، طالما لا يستدعي الهيكل أي من التوابع change
و change_default
و remove
.
التابع remove_column
قابل للاستعادة في حال زوّدته بنوع الحقل كوسيط ثالث. زوّده بخيارات الحقل الأساسي أيضًا، وإلّا لن يتمكّن ريلز من إعادة إنشاء الحقل عند استعادة التهجير:
remove_column :posts, :slug, :string, null: false, default: '', index: true
في حال أردت استعمال أي من التوابع الأخرى، يجب عليه استخدام reversible
لكتابة التوابع up
و down
، بدلًا من استخدام التابع change
.
استخدام reversible
قد تحتاج التهجيرات المعقدة إلى بعض أنواع المعالجة التي لا يعلم السجل الفعال كيفية استعادتها. يمكنك استخدام reversible
لتحديد ما يجب فعله عند تشغيل التهجير وما يجب فعله عند استعادته. مثلًا:
class ExampleMigration < ActiveRecord::Migration[5.0]
def change
create_table :distributors do |t|
t.string :zipcode
end
reversible do |dir|
dir.up do
# add a CHECK constraint
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5) NO INHERIT;
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
SQL
end
end
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
end
باستخدام reversible
، يمكنك ضمان تشغيل التعليمات بالترتيب الصحيح أيضًا. في حال تمّت استعادة التهجير في المثال السابق، يتم تنفيذ الكتلة down
بعد إزالة الحقل home_page_url
وقبل حذف الجدول distributors
.
في بعض الأوقات، تفعل التهجيرات أمورًا لا يمكن استعادتها؛ مثلًا، قد تحذف بعض البيانات. في هذه الحالات، يمكنك اطلاق الاستثناء ActiveRecord::IrreversibleMigration
في الكتلة down
. في حال أراد شخص ما استعادة تهجيرك، ستظهر رسالة خطأ تبلغه بعدم إمكانية ذلك.
استخدام التابعان up
و down
يمكنك أيضًا العودة للشكل القديم من التهجيرات واستخدام التابعان up
و down
بدلًا من التابع change
. يحوي التابع up
التحويلات التي تُجرَى على المخطط، بينما يقوم التابع down
باستعادة التحويلات التي جرت في التابع up
. أي يجب على مخطط قاعدة البيانات ألّا يتغيّر بعد القيام بتنفيذ التابع up
ثمّ التابع down
. مثلًا، إذا قمت بإنشاء جدول في التابع up
، يجب عليك حذفه في التابع down
. من الحكمة أيضًا تنفيذ التحويلات بشكل عكسي تمامًا لما تمّت كتابته في التابع up
. المثال السابق في القسم "استخدام reversible
" مماثل للمثال التالي:
class ExampleMigration < ActiveRecord::Migration[5.0]
def up
create_table :distributors do |t|
t.string :zipcode
end
# add a CHECK constraint
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5);
SQL
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
SQL
drop_table :distributors
end
end
في حال كان تهجيرك غير قابل للاستعادة، يجب عليك اطلاق الاستثناء ActiveRecord::IrreversibleMigration
في التابع down
؛ إذ في حال أراد شخص ما استعادة تهجيرك، ستظهر رسالة خطأ تبلغه بعدم إمكانية ذلك.
استعادة التهجيرات القديمة
يمكنك استخدام قدرات السجل الفعال لاستعادة التهجيرات باستخدام التابع revert
:
require_relative '20121212123456_example_migration'
class FixupExampleMigration < ActiveRecord::Migration[5.0]
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
يقبل التابع revert
أيضًا هيكلًا من التعليمات لاستعادتها. يفيد ذلك في حال أردت استعادة أجزاء من تهجيرات سابقة. مثلًا، لنفترض أنّه تم تنفيذ التهجير ExampleMigration
لكن اكتشف لاحقًا أنّه من المفضّل استخدام عمليات التحقق من السجل الفعال بدلًا من القيد CHECK
لتأكيد الرمز البريدي (zipcode):
class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration[5.0]
def change
revert do
# copy-pasted code from ExampleMigration
reversible do |dir|
dir.up do
# add a CHECK constraint
execute <<-SQL
ALTER TABLE distributors
ADD CONSTRAINT zipchk
CHECK (char_length(zipcode) = 5);
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE distributors
DROP CONSTRAINT zipchk
SQL
end
end
# بقية التجهير لا غبار عليه
end
end
end
كان من الممكن كتابة التهجير نفسه دون استخدام التابع revert
، لكن كان ليتضمّن ذلك بعض الخطوات الإضافية: عكس ترتيب استدعاء create_table
و reversible
، وتبديل create_table
بـ drop_table
، وأخيرًا تبديل up
بـ down
والعكس صحيح. هذا الأمر مُعالج بأكمله في التابع revert
.
ملاحظة: في حال أردت إضافة القيد CHECK
كما في المثال السابق، يجب عليك استخدام الملف structure.sql مثل التابع dump
. اقرأ قسم تعديل المخططات في الأسفل.
تنفيذ التهجيرات
يزوّد ريلز بمجموعة من مهام bin/rails
لتنفيذ مجموعة محددة من التهجيرات.
من المرجَّح أن تكون المهمة الأولى المتعلقة بالتهجيرات والتي ستستخدمها هي rails db:migrate
. في شكلها الأكثر بساطة، تشغّل هذه المهمة التوابع change
أو up
للتهجيرات التي لم يتم تشغيلها بعد. في حال لم يكن هناك تهجيرات لم تُشغّل بعد، تقوم هذه المهمة بالإنهاء. تنفّذ هذه المهمة التهجيرات بالترتيب حسب تاريخ التهجير.
من الجدير بالذكر أن تنفيذ المهمة db:migrate
ينفّذ بدوره المهمة db:schema:dump
، التي تحدّث الملف db/schema.rb ليناسب المخطط الحالي لقاعدة البيانات.
في حال حدّدت إصدارًا معيّنًا، يقوم السجل الفعال بتنفيذ التهجيرات المطلوبة (change
و up
و down
) حتّى يصل للإصدار المعطى. يُحدد الإصدار بالسابقة الرقمية في اسم ملف التهجير. مثلًا، للتهجير حتّى الإصدار 20080906120000، نفذ الأمر التالي:
$ bin/rails db:migrate VERSION=20080906120000
في حال كان الإصدار 20080906120000 أكبر من الإصدار الحالي (أي يتم التهجير للأعلى)، تُنفّذ التوابع change
(أو up
) على كل التهجيرات وصولًا إلى وبما فيها التهجير 20080906120000 ولن تنفذ بعد ذلك أية تهجيرات لاحقة. في حال كان التهجير للأسفل، يتم تنفيذ التوابع down
على كل التهجيرات تنازليًّا حتى الوصول إلى ودون تضمين التهجير 20080906120000.
الاستعادة
مهمّة استعادة التهجير الأخير هي مهمّة متكرّرة أيضًا. مثلًا، في حال ارتكبت خطأ معيّنًا وأردت إصلاحه، بدلًا من البحث عن رقم الإصدار المرافق للتهجير الأخير، يمكنك تشغيل:
$ bin/rails db:rollback
يستعيد هذا الأمر آخر تهجير تمّ تنفيذه، إمّا بعكس التابع change
، أو بتنفيذ التابع down
. في حال أردت استعادة مجموعة من التهجيرات، يمكنك تمرير المعامل STEP
:
$ bin/rails db:rollback STEP=3
يستعيد ذلك آخر ثلاثة تهجيرات.
المهمة db:migrate:redo
هو اختصار للقيام باستعادة التهجيرات وتنفيذها مجددًا. وكما هو الحال في المهمة db:rollback
، يمكنك استخدام المعامل STEP
لتنفيذه على أكثر من تهجير، مثلًا:
$ bin/rails db:migrate:redo STEP=3
لا تقوم أي من المهمّات bin/rails
هذه بأي شيء لا يمكنك فعله مع المهمة db:migrate
. ببساطة، هي أكثر أريحية، بما أنّك لست بحاجة لتحديد إصدار التهجير الذي تريد العودة له.
تهيئة قاعدة البيانات
تقوم المهمة rails db:setup
بإنشاء قاعدة البيانات، وتحميل المخطط وتهيئته بالبيانات اللازمة.
إعادة تهيئة قاعدة البيانات
تقوم المهمة rails db:reset
بحذف قاعدة البيانات وتهيئتها من جديد. تكافئ هذه المهمّة الأمر rails db:drop db:setup
.
ملاحظة: لا يكافئ ذلك تنفيذ كل التهجيرات؛ إذ يستخدم فقط الملف db/schema.rb أو الملف db/structure.sql. في حال عدم إمكانية استعادة تهجير ما، قد لا تساعدك المهمة db:reset
. لقراءة المزيد حول حذف المخطط، اقرأ قسم تحديث المخططات في الأسفل.
تنفيذ تهجيرات محددة
في حال أردت تنفيذ تهجير محدد للأعلى (up) أو للأسفل (down)، يمكنك استخدام المهمات db:migrate:up
أو db:migrate:down
. زوّدها بالإصدار المناسب، وسيتم تنفيذ التوابع change
أو up
أو down
للتهجير الموافق للإصدار المعطى. مثلًا:
$ bin/rails db:migrate:up VERSION=20080906120000
يُنفّذ التهجير 20080906120000 عن طريق تنفيذ التابع change
(أو التابع up
). تقوم هذه المهمة أولًا بفحص فيما إذا كان التهجير هذا مطبّق مسبقًا على قاعدة البيانات، ولن تقوم بأي شيء في حال معرفة السجل الفعال أنّ التهجير قد تم تطبيقه مسبقًا.
تنفيذ التهجيرات في بيئات مختلفة
افتراضيًا، تنفّذ المهمة bin/rails db:migrate
بالبيئة development
. لتنفيذ التهجيرات على بيئة مختلفة، يمكنك تحديدها في متغير البيئة RAILS_ENV
عند تشغيل الأمر. مثلًا، لتنفيذ تهجير ما على بيئة الاختبار (test)، يمكنك تشغيل:
$ bin/rails db:migrate RAILS_ENV=test
تغيير خرج تنفيذ التهجيرات
افتراضيًا، تخبرك التهجيرات بما تفعله حاليًا، وكم من الوقت استغرق تنفيذها؛ ففي حال كان لدينا تهجير ينشئ جدولًا ويضيف فهرسًا عليه، يبدو خرج تنفيذه كالتالي:
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) ========================================
تم تزويد مجموعة من التوابع في التهجيرات للتحكم بخرج تنفيذ التهجيرات:
التابع | الغرض |
---|---|
suppress_messages
|
يأخذ كتلةً كوسيط له، ويزيل أي خرج يتم توليده من الكتلة. |
say
|
يأخذ رسالة كوسيط له، ويظهرها كما هي. يمكن تمرير وسيط منطقيٍّ (boolean) ثانٍ لتحديد ضرورة وضع الفراغات قبل الكلام. |
say_with_time
|
يظهر نصًا، وكم من الوقت استغرق تنفيذ الكتلة. في حال أعادت الكتلة عددًا صحيحًا، يفترض التابع أنّه يمثّل عدد الأسطر المتأثرة. |
مثلًا، ألقِ نظرة فاحصة على هذا التهجير:
class CreateProducts < ActiveRecord::Migration[5.0]
def change
suppress_messages do
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
say "Created a table"
suppress_messages {add_index :products, :name}
say "and an index!", true
say_with_time 'Waiting for a while' do
sleep 10
250
end
end
end
يولّد الخرج التالي:
== CreateProducts: migrating =================================================
-- Created a table
-> and an index!
-- Waiting for a while
-> 10.0013s
-> 250 rows
== CreateProducts: migrated (10.0054s) =======================================
في حال أردت من السجل الفعال ألّا يطبع شيئًا، يمكنك استخدام rails db:migrate VERBOSE=false
لإزالة أي مخرجات.
تغيير التهجيرات الموجودة
قد ترتكب بعض الأخطاء عند كتابتك لتهجير ما. في حال نفّذت هذا التهجير، لن تتمكّن من تعديل التهجير وتنفيذه مجددًا، إذ يفترض ريلز أنّ التهجير تمّ تنفيذه مسبقًا، ولن يقوم بأي شيء عند تشغيلك للأمر rails db:migrate
. يجب عليك استعادة التهجير، ثم تعديله وتشغيل الأمر rails db:migrate
لتنفيذ التهجير المصحّح.
بشكل عام، ليس من المحبّذ تعديل التهجيرات الموجودة؛ إذ ستبني المزيد من العمل لنفسك ولأفراد فريق عملك وتتسبب بصداع في حال كان الإصدار الحالي من التهجير منفّذ على الخوادم المنتجة. بدلًا من ذلك، يجب عليك كتابة تهجير جديد يقوم بالتغيير الذي تريده. تعديل تهجير جديد لم يتم بعد تسليمه لمنصة التحكم بالمصدر (أو بشكل أعم، لم يتم تنفيذه على الخوادم المنتجة) هو مناسب أيضًا بشكل مماثل.
يمكن للتابع revert
أن يفيدك في هذه الحالة، عند كتابة تهجير جديد يلغي تهجيرًا قديمًا ككل أو كجزء.
تعديل المخططات
لمَ نستخدم ملفات المخططات؟
لا يمكن اعتبار التهجيرات - رغم متانتها - مصدرًا موثوقًا لمخطط (schema) قاعدة البيانات، إذ يقع هذا الدور على الملف db/schema.rb أو ملف SQL الذي يولده السجل الفعال عن طريق تحليل قاعدة البيانات. لم تصمّم هذه الملفات ليتم تعديلها، إذ تمثّل حالة قاعدة البيانات الحالية.
لا يوجد حاجة لرفع إصدار جديد من تطبيق ما عن طريق إعادة تنفيذ سجل التهجيرات ككل. من البسيط جدًا والسريع تنفيذ ملف مخطط وحيد لقاعدة البيانات.
مثلًأ، هكذا يتم إنشاء قاعدة بيانات الاختبار: يتم تنزيل قاعدة بيانات التطوير (إمّا إلى db/schema.rb أو db/strucutre.sql)، ومن ثم تحميلها على قاعدة بيانات الاختبار.
تفيدك ملفات المخططات أيضًا عندما تريد الاطلاع على الحقول التي يمتلكها كائن السجل الفعال. هذه المعلومات غير موجودة في مصدر النموذج، وغالبًا ما تكون موزّعة في مجموعة من التهجيرات، لكنّها مجمّعة بشكل جيّد في ملف المخطط. تضيف وتحدّث الجوهرة annotate_models
التعليقات على كل نموذج لشرح مخطّطه إن أردت هذا الحل.
أنواع عمليات تنزيل المخططات
هناك طريقتين لتنزيل المخطط. يحدّد ذلك في الملف config/application.rb عن طريق الإعداد config.active_record_schema_format
، إذ يكون إمّا sql:
أو ruby:
.
في حال تمّ اختيار ruby:
، ستجد المخطط في db/schema.rb. إذا اطلعت على الملف، ستجده مماثلًا لتهجير كبير جدًا:
ActiveRecord::Schema.define(version: 20080906171750) do
create_table "authors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "part_number"
end
end
يتم إنشاء هذا الملف عن طريق تحليل قاعدة البيانات والتعبير عن بنيتها باستخدام create_table
و add_index
والمزيد. باعتبار هذا الملف مستقل عن قاعدة البيانات، يمكن تنفيذه على أي قاعدة بيانات يدعمها السجل الفعال. من الضروري القيام بذلك عند العمل مع تطبيق يقوم بتشغيل مجموعة من قواعد البيانات.
ملاحظة: لا يمكن للملف db/schema.rb التعبير عن الأمور المتعلقة بقاعدة البيانات مثل القوادح (triggers)، والسلاسل (sequences)، والإجراءات المخزنة (stored procedures) أو القيود (check)، ...إلخ. تجدر الملاحظة أنّه في حين يمكن تنفيذ تعليمات SQL في التهجيرات، إلّا أنّه لا يمكن إعادة كتابة ذلك عند تنزيل المخطط. في حال استخدمت إحدى هذه الميّزات، يجب عليك تعيين نمط المخطط الحالي إلى sql:
.
بدلًا من استخدام تنزيل المخططات الخاص بالسجل الفعال، يمكنك تنزيل مخطط قاعدة البيانات باستخدام أداة مناسبة لقاعدة البيانات الحالية (عن طريق المهمة db:structure:dump
) إلى الملف db/structure.sql. مثلًا، من أجل قاعدة بيانات من النوع PostgreSQL، يمكنك استخدام الأداة pg_dump
. ومن أجل MySQL و MariaDB، سيحتوي هذا الملف على خرج SHOW CREATE TABLE
من أجل مجموعة من الجداول.
إن تحميل هذه المخططات يعني ببساطة تنفيذ تعليمات SQL التي تحويها. باستخدام النمط sql:
، لن تتمكّن من تحميل المخطط على قاعدة بيانات من نوع مختلف عن النوع المستخدم لتنزيل ملف المخطط.
ملفات المخططات والتحكم بالمصدر
بما أنّ ملفات المخططات هي المصدر الأساسي لمخطط قاعدة البيانات، من المستحسن جدًا تسليمها لنظام التحكم بالمصدر الخاص بك.
يحوي الملف db/schema.rb على الإصدار الحالي من قاعدة بياناتك. هذا يضمن أنه سيحصل تضارب عند تعديل ملف المخطط من قبل طرفين. عند حدوث هذا التضارب، قم بحلّه يدويًا، واضعًا رقم الإصدار الأكبر من بين الاثنين.
السجل الفعال والسلامة المرجعية
يدّعي السجل الفعال أنّ العمليّات المعقّدة والمنطقية يجب أن تكون جزءًا من طبقة النماذج، وليس من قاعدة البيانات. لذلك، فإنّ الميزات مثل القوادح (triggers) والقيود (constraints) والتي تضفي المنطقية إلى قاعدة بياناتك ليست مستخدمة بكثرة.
التحققات من الصحة مثل validates :foreign_key, uniqueness: true
هي طريقة من الطرق المستخدمة لتضمن نماذجك تكامل البيانات (data integrity). يسمح الخيار dependent:
للارتباطات من حذف الكائنات الأبناء لنموذج ما عند حذف الكائن الأب لها. كما هو الحال مع أي شيء يُشغّل على مستوى التطبيق، لا يمكن لهذه الأوامر أن تضمن السلامة المرجعية (referential integrity)، لذلك يقوم بعض المبرمجون بإضافة قيود المفاتيح الأجنبية لقاعدة البيانات.
رغم أن السجل الفعال لا يوفر هذه الإمكانية، إلّا أن التابع execute
يمكن استخدامه لتنفيذ تعليمات SQL وتحقيق المطلوب.
التهجيرات وبيانات البذور
إن الهدف الأساسي من تهجيرات ريلز هو تنفيذ الأوامر التي تعدّل المخطط باستخدام عملية متناسقة. يمكن استخدام التهجيرات أيضًا لإضافة أو تعديل البيانات. يفيدك ذلك عند العمل مع قاعدة بيانات موجودة مسبقًا، ولا يمكن حذفها وإعادة إنشاؤها، مثل قاعدة البيانات المنتجة.
class AddInitialProducts < ActiveRecord::Migration[5.0]
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
لإضافة بيانات ابتدائية بعد إنشاء قاعدة البيانات، يملك ريلز ميزة "البذور" (seeds) التي تجعل الأمر سهلًا وسريعًا. يفيدك ذلك بشكل خاص عند إعادة تحميل قاعدة البيانات بشكل متكرر في بيئات التطوير والاختبار. من السهل البدء بهذه الميزة: فقط املأ الملف db/seeds.rb بشيفرة روبي، ثم شغّل rails db:seed
.
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
هذه الطريقة أفضل بكثير لتهيئة قاعدة بيانات تطبيق جديد.