ستتعرّف في هذا الدرس على دورة حياة كائنات Active Record، وستتعلم:
- دورة حياة كائنات Active Record.
- كيف تنشئ أساليب دوال الاستدعاء (callback methods) التي تستجيب إلى أحداث في دورة حياة الكائن.
- كيف تنشئ أصناف خاصة التي تجمع السلوك المشترك لدوال الاستدعاء (callbacks).
1- دورة حياة الكائن
يمكن إنشاء، تحديث وتدمير الكائنات أثناء عمل تطبيق Rails، ويوفر Active Record طُرق تُمكنك من التحكم بالتطبيق وبياناته في دورة حياة الكائن .
يسمح لك دوال الاستدعاء بإطلاق منطقي (trigger logic) قبل أو بعد تغيير حالة الكائن.
2- نظرة عامة على دوال الاستدعاء callbacks
دوال الاستدعاء (callbacks) هي أساليب تُستدعى في لحظات معينة من دورة حياة الكائن وتمكنك من كتابة شيفرة برمجية تعمل عند إنشاء، حفظ، تحديث، حذف، تحقق من صحة أو تحميل كائن Active Record من قاعدة البيانات.
2-1 تسجيل دوال الاستدعاء
تحتاج إلى تسجيل دوال الاستدعاء المتوفر حتى تتمكن من استخدامه، فيمكنك وضع دوال الاستدعاء كأساليب عادية، واستخدام أسلوب صنف نمط ماكرو (macro-style) لتسجيلهم كدوال استدعاء:
class User < ApplicationRecord validates :login, :email, presence: true before_validation :ensure_login_has_a_value private def ensure_login_has_a_value if login.nil? self.login = email unless email.blank? end end end
يمكن لأساليب صنف نمط ماكرو (macro-style) تلقي كتلة (block) لذلك استخدم هذا النمط إذا كانت الشيفرة البرمجية داخل الكتلة قصيرة بحيث يمكن وضعها في سطر واحد:
class User < ApplicationRecord
validates :login, :email, presence: true
before_create do
self.name = login.capitalize if name.blank?
end
end
يمكنك تسجيل دوال الاستدعاء ليعمل فقط على بعض أحداث دورة حياة الكائن:
class User < ApplicationRecord
before_validation :normalize_name, on: :create
# :on takes an array as well
after_validation :set_location, on: [ :create, :update ]
private
def normalize_name
self.name = name.downcase.titleize
end
def set_location
self.location = LocationService.query(self)
end
end
من الممارسات الجيدة أن تعلن أساليب دوال الاستدعاء كخاص، لأنه يمكن استدعاؤه من خارج النموذج (Model) إذا كان عامََا وبهذا تنتهك مبدأ تغليف الكائن (object encapsulation).
3- دوال الاستدعاء المتوفرة
هذه قائمة بجميع دوال استدعاء Active Record المتوفر وهو مرتب بنفس ترتيب استدعاءه خلال العمليات:
3-1 إنشاء كائن
-
before_validation
-
after_validation
-
before_save
-
around_save
-
before_create
-
around_create
-
after_create
-
after_save
-
after_commit/after_rollback
3-2 تحديث كائن
-
before_validation
-
after_validation
-
before_save
-
around_save
-
before_update
-
around_update
-
after_update
-
after_save
-
after_commit/after_rollback
3-3 تدمير كائن
-
before_destroy
-
around_destroy
-
after_destroy
-
after_commit/after_rollback
اقتباستحذير:
تعملafter_save
عند الإنشاء والتحديث لكن دائما بعد دوال الاستدعاءafter_create
وafter_update
مهما كان ترتيب تنفيذ نداءات ماكرو (macro calls).
3-4 after_initialize و after_find
ستُستدعى دالة الاستدعاء after_initialize
عند إنشاء كائن Active Record، إما مباشرة عن طريق new
أو عند تحميل السجل record
من قاعدة البيانات، ومن المستحسن تجنب الحاجة إلى تجاوز إنشاء أسلوب Active Record مباشرة.
ستُستدعى دالة الاستدعاء after_find
في كل مرة يُحمّل فيها Active Record سجل من قاعدة البيانات، وستُستدعى after_find
قبل after_initialize
إذا تم تعريفهما.
لا يملك دوال الاستدعاء after_initialize
وafter_find
نظرائهم من نوع before_*
، لكن يمكن تسجيلهم كباقي دوال الاستدعاء Active Record.
class User < ApplicationRecord
after_initialize do |user|
puts "You have initialized an object!"
end
after_find do |user|
puts "You have found an object!"
end
end
>> User.new
You have initialized an object!
=> #<User id: nil>
>> User.first
You have found an object!
You have initialized an object!
=> #<User id: 1>
3-5 after_touch
ستُستدعى دالة الاستدعاء after_touch
كلما يُلمس (touch) كائن Active Record.
class User < ApplicationRecord
after_touch do |user|
puts "You have touched an object"
end
end
>> u = User.create(name: 'Kuldeep')
=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49">
>> u.touch
You have touched an object
=> true
يمكن استخدام هذه الدالة جنبا إلى جنب مع belongs_to
:
class Employee < ApplicationRecord
belongs_to :company, touch: true
after_touch do
puts 'An Employee was touched'
end
end
class Company < ApplicationRecord
has_many :employees
after_touch :log_when_employees_or_company_touched
private
def log_when_employees_or_company_touched
puts 'Employee/Company was touched'
end
end
>> @employee = Employee.last
=> #<Employee id: 1, company_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05">
# triggers @employee.company.touch
>> @employee.touch
Employee/Company was touched
An Employee was touched
=> true
4- تشغيل دوال الاستدعاء
هذه أساليب إطلاق (trigger) دوال الاستدعاء:
-
create
-
create!
-
destroy
-
destroy!
-
destroy_all
-
save
-
save!
-
save(validate: false)
-
toggle!
-
update_attribute
-
update
-
update!
-
Valid?
بالإضافة إلى ذلك، تُطلق (trigger) دالة الاستدعاء after_find
عن طريق أساليب finder التالية:
-
all
-
first
-
find
-
find_by
-
find_by_*
-
find_by_*!
-
find_by_sql
-
last
تعمل دالة الاستدعاء after_initialize
في كل مرة ينشئ فيها كائن صنف class جديد.
اقتباسملاحظة:
أساليب find_by_* و find_by_*! هي finders حيوية تُولد تلقائيا لكل سمة، للمزيد من المعلومات راجع قسم Dynamic finders.
5- تجاوز دوال الاستدعاء
كما هو الحال مع عمليات التحقق (validation)، من الممكن أيضا تخطي دوال الاستدعاء عن طريق الأساليب التالية:
-
decrement
-
decrement_counter
-
delete
-
delete_all
-
increment
-
increment_counter
-
toggle
-
touch
-
update_column
-
update_columns
-
update_all
-
update_counters
يجب استخدام هذه الأساليب بحذر، لأنه قد يُحتفظ بقواعد الأعمال (generated) الهامة ومنطق التطبيق في دوال الاستدعاء، وقد يؤدي تجاوزها دون فهم الآثار المحتملة إلى بيانات غير صالحة.
6- وقف التنفيذ
عند بدء تسجيل دوال استدعاء جديد لنماذجك، سيكون في قائمة الانتظار للتنفيذ، وستشمل هذه القائمة جميع عمليات تحقق (validations) النموذج الخاص بك، ودوال الاستدعاء المسجلة وعمليات قاعدة البيانات التي ستُنفّذ.
تُلف سلسلة (chain) دالة الاستدعاء بشكل كامل في العملية (transaction)، إذا أصدرت أي دالة استدعاء استثناء (exception)، فستتوقف السلسلة التي تعمل و يصدر ROLLBACK، وإذا أردت تعمد إيقاف السلسلة، استخدم:
throw :abort
اقتباستحذير:
سيعاد إصدار أي استثناء (ما عداActiveRecord::Rollback
أوActiveRecord::RecordInvalid
) عن طريق Rails بعد إيقاف سلسلة دالة الاستدعاء.
قد يكسر أي استثناء ماعداActiveRecord::Rollback
أوActiveRecord::RecordInvalid
شيفرتك البرمجية التي لا تتوقع إصدار استثناء مثل أساليبsave
وupdate_attributes
(والتي تُرجع في العادة قيمة منطقيةtrue
أوfalse
).
7- دوال استدعاء علائقي
يعمل دوال الاستدعاء من خلال علاقات النموذج model، ويمكن تعريفه عن طريقه، لنفترض أن المستخدم لديه مقالات عديدة، ويجب حذف مقالات المستخدم في حالة حذف المستخدم، لنضف دالة استدعاء after_destroy
إلى نموذج User لترتبط علائقيا بنموذج Model:
class User < ApplicationRecord
has_many :articles, dependent: :destroy
end
class Article < ApplicationRecord
after_destroy :log_destroy_action
def log_destroy_action
puts 'Article destroyed'
end
end
>> user = User.first
=> #<User id: 1>
>> user.articles.create!
=> #<Article id: 1, user_id: 1>
>> user.destroy
Article destroyed
=> #<User id: 1>
8- دوال الاستدعاء المشروط
كما هو الحال مع عمليات التحقق Validation، يمكن أيضا جعل أسلوب دالة الاستدعاء مشروط بشرط معين عن طريق استخدام خيارات :if
و :unless
والتي تأخذ رمز (symbol) أو Proc أو مصفوفة (Array).
يمكنك استخدام خيار :if
عندما تريد تحديد شروط استدعاء دالة الاستدعاء، وإذا أردت وضع شروط لعدم استدعاء دالة الاستدعاء يمكنك استخدام :unless
.
8-1 استخدام :if و :unless مع Symbol
يمكنك ربط خيارات :if
و :unless
مع رمز (Symbol) مطابق لاسم الأسلوب الذي سيُستدعى قبل دالة الاستدعاء.
لن تُنفّذ دالة الاستدعاء عند استخدام خيار :if
إذا ارجع الأسلوب false
، وعند استخدام :unless
لن تُنفّذ دالة الاستدعاء إذا أرجع الأسلوب true
، وهذا الخيار هو الأكثر شيوعا.
من الممكن أيضا باستخدام هذا الشكل من التسجيل تسجيل predicates
مختلفة، التي يتم استدعاؤها للتحقق ما إذا كان يجب تنفيذ دالة الاستدعاء.
class Order < ApplicationRecord
before_save :normalize_card_number, if: :paid_with_card?
End
8-2 استخدام :if و :unless مع Proc
أخيرا، من الممكن ربط :if
و :unless
مع كائن Proc، وهذا الخيار هو الأنسب عند كتابة أساليب تحقق (validation) قصيرة، وفي العادة من سطر واحد:
class Order < ApplicationRecord
before_save :normalize_card_number,
if: Proc.new { |order| order.paid_with_card? }
end
8-3 شروط متعددة لدوال الاستدعاء
عند كتابة دوال استدعاء شرطي، من الممكن المزج بين :if
و :unless
في نفس إعلان دالة الاستدعاء:
class Comment < ApplicationRecord
after_create :send_email_to_author, if: :author_wants_emails?,
unless: Proc.new { |comment| comment.article.ignore_comments? }
end
9- أصناف دالة الاستدعاء
في بعض الأحيان، تكون أساليب دالة الاستدعاء التي ستكتبها مفيدة وملائمة لإعادة استخدامها من قبل نماذج أخرى، لذا يوفر Active Record إمكانية إنشاء أصناف تغلف أساليب دالة الاستدعاء، لذا يصبح من السهل إعادة استخدامها.
هذا مثال أنشئنا فيه صنف مع دالة استدعاء after_destroy
لنموذج PictureFile
:
class PictureFileCallbacks
def after_destroy(picture_file)
if File.exist?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
عند الإعلان داخل صنف -كما في المثال أعلاه-، ستتلقى أساليب دالة الاستدعاء كائن النموذج كعامل، ونستطيع الآن استخدام صنف دالة الاستدعاء في النموذج:
class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks.new
end
لاحظ أننا بحاجة إلى إنشاء كائن PictureFileCallbacks
جديد لأننا أعلنا دالة الاستدعاء الخاصة بنا على أنها مثيل أسلوب(instance method) ،وستستفيد من هذا خاصة إذا استخدمت دوال الاستدعاء حالة الكائن المثيل، ومع ذلك، في الكثير من الأحيان، سيكون من المنطقي إعلان دوال الاستدعاء كأساليب الصنف:
class PictureFileCallbacks
def self.after_destroy(picture_file)
if File.exist?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
لن يكون من الضروري إنشاء كائن PictureFileCallbacks
إذا أُعلن عن أسلوب دالة الاستدعاء بهذه الطريقة.
class PictureFile < ApplicationRecord
after_destroy PictureFileCallbacks
end
يمكنك إعلان العدد الذي تريده من دوال الاستدعاء داخل أصناف دالة الاستدعاء.
10- عمليات دوال الاستدعاء
يوجد دالتي استدعاء إضافيتين تعمل عند الانتهاء من عملية قاعدة البيانات: after_commit
و after_rollback
، وهما مشابهين لـ after_save
إلا أنهم لا يعملون حتى يُنفّذ التغيير في قاعدة البيانات أو يتم التراجع عنه، وهما مفيدان للغاية عندما تحتاج نماذج active record
إلى التفاعل مع الأنظمة الخارجية والتي هي ليست جزء من عملية قاعدة البيانات.
على سبيل المثال، فكر في المثال السابق حيث احتاج نموذج PictureFile
إلى حذف ملف بعد تدمير السجل، إذا أُصدر استثناء بعد استدعاء دالة الاستدعاء after_destroy
و أُرجعت العملية، فسيحذف الملف وسيبقى النموذج في حالة غير متناسقة، على سبيل المثال، افترض أن picture_file_2
في الشيفرة البرمجية أدناه غير صحيح و أصدر أسلوب save!
خطأََ:
PictureFile.transaction do
picture_file_1.destroy
picture_file_2.save!
End
يمكننا تجنب هذه المشكلة باستخدام دالة الاستدعاء after_commit
:
class PictureFile < ApplicationRecord
after_commit :delete_picture_file_from_disk, on: :destroy
def delete_picture_file_from_disk
if File.exist?(filepath)
File.delete(filepath)
end
end
end
اقتباسملاحظة:
يُحدد الخيار:on
عند تشغيل الاستدعاء، وإذا لم تضعه، ستشتغل دالة الاستدعاء عند كل عمل.
بما أنه من الشائع استخدام after_commit
عند الإنشاء والتحديث والحذف، فتوجد أسماء مستعارة aliases لهذه العمليات:
-
after_create_commit
-
after_update_commit
-
after_destroy_commit
class PictureFile < ApplicationRecord
after_destroy_commit :delete_picture_file_from_disk
def delete_picture_file_from_disk
if File.exist?(filepath)
File.delete(filepath)
end
end
end
اقتباستحذير:
تُستدعى دوال الاستدعاءafter_commit
وafter_rollback
لجميع النماذج التي تنشئ، تُحدّث أو تُدمّر داخل كتلة العملية (transaction)، ومع ذلك، إذا أصدر استثناء داخل واحد من دوال الاستدعاء، فلن تنفذ بقية أساليبafter_commit
وafter_rollback
المتبقية، وعلى هذا النحو، إذا كانت شيفرة (code) دالة الاستدعاء تصدر استثناء، ستحتاج إلى التعامل معه داخل دالة الاستدعاء من أجل السماح بالعمل لبقية دوال الاستدعاء.
المصدر:
توثيقات Ruby on Rails
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.