هشام رزق الله

ستتعرّف في هذا الدرس على دورة حياة كائنات 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





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن