ملحقات الدعم الفعال الأساسية في ريلز
الدعم الفعَّال (Active Support) هو مكوّن ريلز المسؤول عن توفير ملحقات لغة روبي والأدوات المساعدة والأشياء الأخرى المعترضة.
كما يوفر حدًا أدنى من المعرفة الواسعة على مستوى اللغة يستهدف كل من تطوير تطبيقات ريلز، وتطوير ريلز نفسه. بعد قراءة هذا الدليل، ستتعلم:
- ماهيّة الملحقات الأساسية
- كيفية تحميل كل الملحقات.
- كيفية انتقاء الملحقات التي تريدها فقط.
- ماهيّ الملحقات التي يوفرها الدعم الفعَّال.
كيفية تحميل الملحقات الأساسية
الدعم الفعَّال المستقل
لا يحمّل الدعم الفعَّال (Active Support) أي شيء افتراضيًّا من أجل عدم شغل أية مساحة بل يُقسَّم لأجزاء صغيرة بحيث تستطيع تحميل ما تحتاجه فقط؛ كما أنه يملك بعض نقاط انطلاق ملائمة لتحميل الملحقات ذات الصلة مرّة واحدة أو حتى كل الملحقات.
يكون ذلك بعد استعمال الأمر require
البسيط:
require 'active_support'
لا تستجيب الكائنات (objects) حتى مع ?blank
. فلننظر لكيفيّة تحميله لتعريفه (definition).
انتقاء تعريف
أخف طريقة للحصول على ?blank
هو انتقاء الملف الذي يُعرّفه.
من أجل كل تابع مُعرّف كملحق أساسي، يحتوي هذا الدليل على ملاحظة توضح أين عُرّف مثل هذا التابع. في حالة ?blank
، تقول الملاحظة:
ملاحظة: عُرِّف في active_support/core_ext/object/blank.rb.
ممّا يعني أنك تستطيع طلبه على النحو التالي:
require 'active_support'
require 'active_support/core_ext/object/blank'
عُدّل "الدعم الفعَّال" بعناية بحيث لا يُحمّل انتقاء ملف (مثل المثال السابق) إلّا الاعتماديّات المطلوبة بشدّة إن وُجدَت.
تحميل الملحقات الأساسية المُجمّعة
المستوى التالي هو ببساطة تحميل جميع الملحقات إلى Object
. كقاعدة عامة، تتوفّر الملحقات لـ SomeClass
مرّة واحدة عن طريق تحميل active_support/core_ext/some_class.
وبالتالي تحميل جميع الملحقات لـ Object
(بما في ذلك ?blank
) يكون بالشكل التالي:
require 'active_support'
require 'active_support/core_ext/object'
تحميل جميع الملحقات الأساسية
قد تفضل ببساطة تحميل جميع الملحقات الأساسية وهناك ملف لذلك:
require 'active_support'
require 'active_support/core_ext'
تحميل جميع الدعم الفعَّال
وأخيرًا، إن رغبت في توفير الدعم الفعَّال (Active Support) كاملًا، ما عليك سوى كتابة:
require 'active_support/all'
هذا لا يضع فعلًا الدعم الفعَّال بأكمله في الذاكرة مسبقًا، بل تُعدُّ بعض الأشياء عن طريق autoload
، لذلك تُحمّل فقط إن استُخدمت.
الدعم الفعَّال ضمن تطبيق ريلز
يحمّل تطبيق ريلز كل الدعم الفعَّال ما لم تكن قيمة config.active_support.bare
مساويةً إلى true
. سُيحمّل التطبيق في هذه الحالة ما ينتقيه الإطار نفسه لاحتياجاته الخاصة فقط، ويظل بإمكانه الانتقاء بنفسه على أي مستوى من مستويات التقسيم (granularity level)، كما وُضّح في القسم السابق.
ملحقات لجميع الكائنات
?blank
و ?present
تُعتبر القيم التالية فارغة في تطبيق ريلز:
nil
وfalse
- السلاسل النصيّة التي تتكوّن من مسافات بيضاء فقط (انظر الملاحظة أدناه)
- المصفوفات وجداول Hash الفارغة
- أي كائن آخر يستجيب لـ
?empty
وهو خالي.
تنويه: تستخدم التوابع الخبرية (predicate) للسلاسل النصيّة صنف الحرف الواعي بترميز اليونيكود [:space:]
، لذلك يُعتبر U+2029 (فاصل الفقرات) مثلًا مسافة بيضاء.
تحذير: لاحظ عدم ذكر الأعداد فيما سبق، إذ لا يعد العدد 0 و 0.0 على وجه الخصوص قيمةً فارغةً.
على سبيل المثال، يستخدم هذا التابع من ActionController::HttpAuthentication::Token::ControllerMethods
التابع ?blank
للتحقّق من وجود أي شيء (token):
def authenticate(controller, &login_procedure)
token, options = token_and_options(controller.request)
unless token.blank?
login_procedure.call(token, options)
end
end
يُعادل التابع ?present
التابع ?blank!
. هذا المثال مُقتطف من ActionDispatch::Http::Cache::Response
:
def set_conditional_cache_control!
return if self["Cache-Control"].present?
...
end
ملاحظة: مُعرّف في active_support/core_ext/object/blank.rb.
التابع presence
يعيد التابع presence
مُستقبِله في حالة تحقق ?present
أو يعيد nil
في ما عدا ذلك. وهو مفيد بالتعابير مثل هذه:
host = config[:host].presence || 'localhost'
ملاحظة: مُعرّف في active_support/core_ext/object/blank.rb.
التابع ?duplicable
يمكن تكرار معظم الكائنات عبر dup
أو clone
في روبي 2.4 باستثناء التوابع وأرقام معينة. على الرغم من أن الإصدار 2.2 و 2.3 من روبي لا يقدران على تكرار nil
، و false
، و true
والرموز إضافةً لنُسخ (instances) مثل Float
و Fixnum
و Bignum
.
"foo".dup # => "foo"
"".dup # => ""
1.method(:+).dup # => TypeError: allocator undefined for Method
Complex(0).dup # => TypeError: can't copy Complex
يُوفّر الدعم الفعَّال التابع ?duplicable
لاستعلام كائن حول هذا:
"foo".duplicable? # => true
"".duplicable? # => true
Rational(1).duplicable? # => false
Complex(1).duplicable? # => false
1.method(:+).duplicable? # => false
يطابق التابع ?duplicable
التابع dup
، حسب إصدار روبي المتوفر.
لذلك نجد في الإصدار 2.4:
nil.dup # => nil
:my_symbol.dup # => :my_symbol
1.dup # => 1
nil.duplicable? # => true
:my_symbol.duplicable? # => true
1.duplicable? # => true
بينما نجد في الإصدار 2.2 والإصدار 2.3:
nil.dup # => TypeError: can't dup NilClass
:my_symbol.dup # => TypeError: can't dup Symbol
1.dup # => TypeError: can't dup Fixnum
nil.duplicable? # => false
:my_symbol.duplicable? # => false
1.duplicable? # => false
تحذير: يقدر أي صنف على منع التكرار عبر حذف التابع dup
والتابع clone
منه أو إطلاق استثناء منهما. وبالتالي، فقط التابع rescue
يقدر على معرفة ما إذا كان كائن ما قابلًا للتكرار. يعتمد التابع ?duplicable
على القائمة الثابتة (hard-coded list) أعلاه لكنه أسرع بكثير من rescue
. استخدمه فقط إن تيقنت أن القائمة الثابتة كافية في حالة استخدامك.
ملاحظة: مُعرّف في active_support/core_ext/object/duplicable.rb.
التابع deep_dup
يعيد التابع deep_dup
نسخة عميقة (deep copy) من كائن معين. عادةً، عندما تستخدم dup
على كائن يحتوي على كائنات أخرى، لا يمتد التأثير لهم، لذلك ينشئ روبي نسخة ضحلة (shallow copy) من الكائن. على سبيل المثال، إن كانت لديك مصفوفة تحتوي على سلسلة نصيّة، فستبدو كالتالي:
array = ['string']
duplicate = array.dup
duplicate.push 'another-string'
# the object was duplicated, so the element was added only to the duplicate
array # => ['string']
duplicate # => ['string', 'another-string']
duplicate.first.gsub!('string', 'foo')
# first element was not duplicated, it will be changed in both arrays
array # => ['foo']
duplicate # => ['foo', 'another-string']
حصلنا كما ترى على كائن آخر بعد تكرار نسخة Array
بحيث يمكننا تعديلها بينما يظل الكائن الأصلي بدون تغيير. هذا لا ينطبق على عناصر المصفوفة. بما أن التابع dup
لا ينشئ نسخة عميقة، تظل السلسلة داخل المصفوفة نفس الكائن.
إن احتجت لنسخة عميقة من كائن ما، عليك استخدام التابع deep_dup
. على سبيل المثال:
array = ['string']
duplicate = array.deep_dup
duplicate.first.gsub!('string', 'foo')
array # => ['string']
duplicate # => ['foo']
سيعيد التابع deep_dup
الكائن كما هو إن كان غير قابل للتكرار:
number = 1
duplicate = number.deep_dup
number.object_id == duplicate.object_id # => true
ملاحظة: مُعرّف في active_support/core_ext/object/deep_dup.rb.
التابع try
أبسط طريقة لمناداة تابع على كائن ليس nil
هي بالعبارات الشرطية لكنها تضيف فوضى غير ضرورية. البديل هو استخدام التابع try
. التابع try
يماثل Object.send
إلا أنه يعيد القيمة nil
إن أُرسل إلى nil
.
على سبيل المثال:
# try بدون
unless @number.nil?
@number.next
end
# try مع
@number.try(:next)
مثال آخر هو هذه التعليمات البرمجيّة من ActiveRecord::ConnectionAdapters::AbstractAdapter
حيث يمكن أن تكون قيمة @logger
هي nil
. تستطيع أن ترى أن التعليمات البرمجيّة تستخدم التابع try
وتتجنب بذلك إجراء أي اختبار غير ضروري:
def log_info(sql, name, ms)
if @logger.try(:debug?)
name = '%s (%.1fms)' % [name || 'SQL', ms]
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end
يمكن أيضًا مناداة التابع try
بدون أي وسيط (arguments) لكن مع كتلة (block) تُنفّذ فقط إن لم يكن الكائن هو nil
:
@person.try { |p| "#{p.first_name} #{p.last_name}" }
لاحظ كيف أن التابع try
"سيبتلع" أخطاء غياب التابع (no-method errors) مع إعادة nil
عوض ذلك. إن كنت تريد الحماية من الأخطاء المطبعية، فاستخدم التابع try!
:
@number.try(:nest) # => nil
@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer
ملاحظة: مُعرّف في active_support/core_ext/object/try.rb.
التابع class_eval(*args, &block)
تستطيع تقييم التعليمة البرمجيّة في سياق أي صنف منفرد (singleton class) لأي كائن باستخدام التابع class_eval
:
class Proc
def bind(object)
block, time = self, Time.current
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
method = instance_method(method_name)
remove_method(method_name)
method
end.bind(object)
end
end
ملاحظة: مُعرّف في active_support/core_ext/kernel/singleton_class.rb.
التابع (acts_like?(duck
يوفر التابع ?acts_like
طريقةً للتحقق من كون صنف ما يعمل مثل صنف آخر بناءً على عُرفٍ بسيط هو: يعرف صنفٌ يُوفّر نفس الواجهة التي يوفرها String
بالشكل:
def acts_like_string?
end
وهي مجرّد علامة، إذ شكله والقيمة التي يعيدها غير ذي صلة. بعد ذلك، تستطيع تعليمات العميل البرمجيّة (client code) الاستعلام للتحقّق من أمان النوع (duck type safeness) بهذه الطريقة:
some_klass.acts_like?(:string)
ملاحظة: مُعرّف في active_support/core_ext/object/acts_like.rb.
التابع to_param
تستجيب كل الكائنات في ريلز للتابع to_param
المُصمّم لإعادة شيء يمثلهم كقيم في سلسلة استعلام أو كأجزاء URL.
يستدعي التابع to_param
التابع to_s
افتراضيًّا:
7.to_param # => "7"
لا يجب تهريب القيمة التي يعيدها التابع to_param
:
"Tom & Jerry".to_param # => "Tom & Jerry"
تعيد عدّة أصناف في ريلز تعريف هذا التابع.
على سبيل المثال، تعيد الكائنات nil
و true
و false
أنفسها. يستدعي Array.to_param
التابع to_param
على العناصر ثم يجمع النتائج مع المحرف "/":
[0, true, String].to_param # => "0/true/String"
الجدير بالذكر هو أن نظام توجيه إطار العمل ريلز يستدعي التابع to_param
على النماذج (models) للحصول على قيمة المحتوى النائب (placeholder).
يعيد ActiveRecord::Base.to_param
مُعرّف النموذج (id) لكن بإمكانك تغيير سلوك ذاك التابع في نماذجك. مثلًا انطلاقًا من:
class User
def to_param
"#{id}-#{name.parameterize}"
end
end
نتحصّل على:
user_path(@user) # => "/users/357-john-smith"
تحذير: يجب أن تكون وحدات التحكّم على دراية بأية إعادة تعريف للتابع to_param
لأنه عند قدوم طلب مثل المثال السابق، ستكون السلسلة "357-john-smith" هي قيمة params[:id]
.
ملاحظة: مُعرّف active_support/core_ext/object/to_param.rb.
التابع to_query
باستثناء الجداول Hash، عندما تعطي هذا التابع القيمة key
دون تهريب (unescaped)، يبني جزءًا من سلسلة الاستعلام النصيّة التي ستعيِّن المفتاح (key) إلى ما يعيده التابع to_param
. مثلًا، انطلاقًا من:
class User
def to_param
"#{id}-#{name.parameterize}"
end
end
نتحصّل على:
current_user.to_query('user') # => "user=357-john-smith"
يُهرّب هذا التابع ما يحتاجه من كلا المفتاح (key) والقيمة (value):
account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"
بحيث يكون خَرجُها (output) جاهزًا للاستخدام في سلسلة استعلام نصيّة.
تعيد المصفوفات نتيجة تطبيق to_query
على كل عنصر باستخدام []key
كمفتاح ثم تجمع النتيجة مع "&":
[3.4, -45.6].to_query('sample')
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
كما تستجيب الجداول Hash إلى to_query
ولكن مع توقيع مختلف. إذا لم يتم تمرير أي وسيط، تقوم إحدى الاستدعاءات بتوليد سلسلة مرتَّبة من تعيينات المفاتيح/القيم التي تستدعي to_query(key)
على قيمها. بعد ذلك، تَجمَع النتيجة مع "&":
{c: 3, b: 2, a: 1}.to_query # => "a=1&b=2&c=3"
يقبل التابع Hash.to_query
مجال اسم اختياري للمفاتيح:
{id: 89, name: "John Smith"}.to_query('user')
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"
ملاحظة: مُعرّف في active_support/core_ext/object/to_query.rb.
التابع With_options
يُوفّر التابع with_options
طريقة لاحتساب الخيارات الشائعة في سلسلة من استدعاءات التابع.
يمنح with_options
كائنًا وكيلًا (proxy object) إلى كتلة لمّا يُعطى جدول hash للخيارات الافتراضية. وداخل الكتلة، يُعاد توجيه التوابع التي استدعيت على الوكيل إلى المُسقبِل مع دمج خياراته. وهكذا تتخلّص مثلًا من التكرارات في:
class Account < ApplicationRecord
has_many :customers, dependent: :destroy
has_many :products, dependent: :destroy
has_many :invoices, dependent: :destroy
has_many :expenses, dependent: :destroy
end
بهذه الطريقة:
class Account < ApplicationRecord
with_options dependent: :destroy do |assoc|
assoc.has_many :customers
assoc.has_many :products
assoc.has_many :invoices
assoc.has_many :expenses
end
end
قد يُعبّر هذا المصطلح عن "التجميع" (grouping) للقارئ أيضًا. لنفترض مثلًا أنك تريد إرسال رسالة إخبارية تعتمد لُغتها على لغة المستخدم. تستطيع بموضع ما من المٌرسِل (mailer) تجميع أجزاء مُعتمدة على الإعدادات المحلية على النحو التالي:
I18n.with_options locale: user.locale, scope: "newsletter" do |i18n|
subject i18n.t :subject
body i18n.t :body, user_name: user.name
end
بما أنّ with_options
يعيد توجيه الاستدعاءات إلى مُستقبِلها، فيمكن أن تكون متداخلة. يدمج كل مستوى تشعب إعداداته الافتراضية الموروثة بالإضافة إلى قيمه الافتراضية الخاصة.
ملاحظة: مُعرّف في active_support/core_ext/object/with_options.rb.
دعم JSON
يُوفّر الدعم الفعَّال تعريف استخدام (implementation) أفضل للتابع to_json
من جوهرة json (أي json gem) التي تُوفّر عادةً كائنات روبي. وذلك لأن بعض الأصناف، مثل Hash
و OrderedHash
و Process::Status
تحتاج لمعالجة خاصّة كي تُوفّر تمثيل JSON مناسب.
ملاحظة: مُعرّف في active_support/core_ext/object/json.rb.
متغيرات النسخة (Instance Variables)
يُوفّر الدعم الفعال (Active Support) عدّة توابع لتيسير الوصول إلى متغيرات النسخة.
Instance_values
يعيد التابع instance_values
جدول Hash يعين أسماء مُتغّيرات النسخة بدون "@" إلى قيمها المقابلة. المفاتيح هي سلاسل نصيّة:
class C
def initialize(x, y)
@x, @y = x, y
end
end
C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
ملاحظة: مُعرّف في active_support/core_ext/object/instance_variables.rb.
Instance_variable_names
يعيد التابع instance_variable_names
مصفوفة. يتضمّن كل اسم فيها العلامة "@".
class C
def initialize(x, y)
@x, @y = x, y
end
end
C.new(0, 1).instance_variable_names # => ["@x", "@y"]
ملاحظة: مُعرّف في active_support/core_ext/object/instance_variables.rb.
إسكات التحذيرات والاستثناءات
يُغيّر التابعان silence_warnings
و enable_warnings
قيمة VERBOSE$
كما يُناسب خلال فترة الكتلة الخاصة بهما ثم يعيدان ضبطها (reset) بعد ذلك:
silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
إسكات الاستثناءات مُمكن أيضًا مع suppress
. يتلقى هذا التابع عددًا عشوائيًّا من أصناف الاستثناءات. إن رُفع استثناءٌ أثناء تنفيذ الكتلة وكان ?kind_of
أيًّا من الوسائط، فسيلتقطها suppress
ويرد بصمت. خلا ذلك، لن يُلتقَط الاستثناء:
# ًإن قُفِل المُستخدم يُفقد مُعامل الزيادة لكن هذا ليس مشكلة
suppress(ActiveRecord::StaleObjectError) do
current_user.increment! :visits
end
ملاحظة: مُعرّف في active_support/core_ext/kernel/reporting.rb.
التابع ?in
يختبر التابع الخبري ?in
(أي predicate) كون كائن ما داخل كائن آخر. سيطلق الاستثناء ArgumentError
إن لم يستجب الوسيط المُمرّر لـ ?include
.
إليك المثال التالي:
1.in?([1,2]) # => true
"lo".in?("hello") # => true
25.in?(30..50) # => false
1.in?(1) # => ArgumentError
مُعرّف في active_support/core_ext/object/inclusion.rb.
من ملحقات إلى وحدة (Extensions to Module)
الخاصيّات (Attributes)
التابع Alias_attribute
تمتلك خاصيّات النموذج قارئًا، وكاتبًا، وتابعًا خبريًّا (predicate). تستطيع إنشاء اسم بديل لخاصيّة نموذج تملك هذه التوابع الثلاثة المعرَّفة من أجلك مرّةً واحدةً. كما هو الحال في توابع التسمية البديلة الأخرى، يكون الاسم الجديد هو الوسيط الأول والاسم القديم هو الثاني (يمكنك تذكّر ترتيبها بتذكّر ترتيب أي عمليّة إسناد):
class User < ApplicationRecord
# "login" تستطيع الاشارة إلى حقل البريد الإلكتروني باسم
# قد يكون هذا ذا معنى بالنسبة لشيفرة المصادقة
alias_attribute :login, :email
end
ملاحظة: مُعرّف في active_support/core_ext/module/aliasing.rb.
الخاصيّات الداخلية
يصبح تضارب الأسماء محتملًا وخطرًا عندما تُحدّد خاصيّة في صنف من المفترض أن يكون فرعيًّا. هذا مهم بشكل خاص للمكتبات.
يُعرّف الدعم الفعال وحدات الماكرو attr_internal_reader
و attr_internal_writer
و attr_internal_accessor
. وهم يتصرّفون مثل نظرائهم *_attr
في روبي باستثناء أن تسميتهم لمُتغيّر النسخة الضمنيّة تجعل التضارب أقلّ احتمالًا.
يُعد الماكرو attr_internal
مرادفًا لـ attr_internal_accessor
:
# الكتبة
class ThirdPartyLibrary::Crawler
attr_internal :log_level
end
# شيفرة العميل
class MyCrawler < ThirdPartyLibrary::Crawler
attr_accessor :log_level
end
في المثال السابق، قد لا ينتمي log_level:
للواجهة العامة للمكتبة ولا يُستخدم إلا للتطوير. لكن بفضل attr_internal
، تُعرّف شيفرة العميل بدون إدراك للصراع المحتمل والأصناف الفرعيّة وتعرِّف log_level:
الخاص بها. بفضل attr_internal
، لا يحصل تصادم.
يُسمّى متغير النسخة الداخلي باستخدام العلامة "@" في أولّه مثل log_level_@
في المثال أعلاه. وهذا قابل للإعداد (configurable) عبر Module.attr_internal_naming_format
. تستطيع مع ذلك تمرير أي سلسلة تنسيق شبيهة بالمستعملة في التابع sprintf
مع البادئة @
و %s
في موضع ما حيث سيوضع الاسم. الافتراضي هو "s%_@".
يستخدم ريلز الخاصيّات الداخلية في بعض المواضع مثلًا بالعروض:
module ActionView
class Base
attr_internal :captures
attr_internal :request, :layout
attr_internal :controller, :template
end
end
ملاحظة: مُعرّف في active_support/core_ext/module/attr_internal.rb.
خصائص الوحدة
وحدات الماكرو mattr_reader
و mattr_writer
و mattr_accessor
هي نفس وحدات الماكرو *_cattr
المُعرّفة للصنف. في الواقع وحدات الماكرو *_cattr
هي ببساطة الأسماء البديلة للماكرو *_mattr
. تحقق من خاصيّات الصنف في الأسفل.
آليات الاعتماديّات مثلًا تستخدمهم:
module ActiveSupport
module Dependencies
mattr_accessor :warnings_on_first_load
mattr_accessor :history
mattr_accessor :loaded
mattr_accessor :mechanism
mattr_accessor :load_paths
mattr_accessor :load_once_paths
mattr_accessor :autoloaded_constants
mattr_accessor :explicitly_unloadable_constants
mattr_accessor :constant_watch_stack
mattr_accessor :constant_watch_stack_mutex
end
end
ملاحظة: مُعرّف في active_support/core_ext/module/attribute_accessors.rb.
الآباء
التابع parent
يعيد التابع parent
مع وحدة مُسمّاة متشعبة (nested named module) الوحدة التي تحتوي على الثابت المقابل الخاص بها:
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.parent # => X::Y
M.parent # => X::Y
إن كانت الوحدة مجهولة الاسم أو تنتمي إلى المستوى الأعلى، فيعيد التابع parent
الكائن Object
.
تحذير: لاحظ أنه في هذه الحالة، يعيد parent_name
القيمة nil
.
ملاحظة: مُعرّف في active_support/core_ext/module/introspection.rb.
التابع parent_name
يعيد التابع parent_name
في وحدة مُسمّاة متشعبة (nested named module) الاسم المؤهل الكامل للوحدة التي تحتوي على الثابت المقابل الخاص بها:
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.parent_name # => "X::Y"
M.parent_name # => "X::Y"
يعيد التابع parent_name
بالنسبة للوحدات مجهولة الاسم (anonymous modules) أو ذات المستوى العالي (top-level modules) القيمة nil
.
تحذير: لاحظ أن التابع parent
في هذه الحالة يعيد Object
.
ملاحظة: مُعرّف في active_support/core_ext/module/introspection.rb.
التابع parents
يستدعي التابع parents
التابع parent
على المُستقبل وحتى يصل صعودًا إلى Object
. تعاد السلسلة في مصفوفة وتتوضع من الأسفل إلى الأعلى:
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.parents # => [X::Y, X, Object]
M.parents # => [X::Y, X, Object]
ملاحظة: مُعرّف في active_support/core_ext/module/intros·
الوحدات المجهولة
قد أو قد لا تحتوي وحدةٌ (module) على اسم:
module M
end
M.name # => "M"
N = Module.new
N.name # => "N"
Module.new.name # => nil
تستطيع التحقّق من امتلاك وحدة لاسم بفضل التابع الخبري ?anonymous
:
module M
end
M.anonymous? # => false
Module.new.anonymous? # => true
لاحظ أن كون الوحدة غير قابلة للوصول لا يعني أنها بلا اسم:
module M
end
m = Object.send(:remove_const, :M)
m.anonymous? # => false
رغم أن الوحدة المجهولة هي وحدة مستحيلة الوصول حسب التعريف.
ملاحظة: مُعرّف في active_support/core_ext/module/anonymous.rb.
تفويض التوابع
delegate
يوفّر الماكرو delegate
طريقةً سهلةً لإعادة توجيه التوابع.
فلنتخيّل مثلًا أنّ المستخدمين في تطبيق ما يملكون معلومات تسجيل دخول في النموذج User
ولكن الاسم والبيانات الأخرى في نموذج منفصل هو Profile
:
class User < ApplicationRecord
has_one :profile
end
ستحصل على اسم المستخدم عبر ملفه الشخصي بتلك الاعدادات، user.profile.name
، ولكن من المفيد الوصول إلى مثل هذه الخاصيّة مباشرة:
class User < ApplicationRecord
has_one :profile
def name
profile.name
end
end
هذا ما يفعله التابع delegate
:
class User < ApplicationRecord
has_one :profile
delegate :name, to: :profile
end
هذه الطريقة أقصر وأوضح.
يجب أن يكون هذا التابع عامًّا (public) في الهدف.
يقبل الماكرو delegate
عدّة توابع:
delegate :name, :age, :address, :twitter, to: :profile
يجب أن يُصبح الخيار to:
تعبيرًا يُساوي الكائن الذي فُوّض التابع إليه. عادةً، سلسلة نصية أو رمز. تُقيَّمُ مثل هذه التعابير في سياق المستقبل (receiver):
# Rails التفويض إلى الثابت
delegate :logger, to: :Rails
# التفويض إلى صنف المستقبل
delegate :table_name, to: :class
تحذير: إذا كانت قيمة الخيار :prefix هي true
وهو أقل شيوعًا، انظر أدناه.
ينتشر الاستثناء افتراضيًّا إن اطلق التفويض NoMethodError
مع كون الهدف nil
. تستطيع أن تطلب إرجاع nil
بدلًا من ذلك بفضل الخيار allow_nil:
:
delegate :name, to: :profile, allow_nil: true
مع allow_nil:
، يعيد الاستدعاء user.name
القيمة nil
إن لم يملك المستخدم ملفًّا شخصيًّا.
يضيف الخيار prefix:
بادئة (prefix) إلى اسم التابع المُولّد. قد يكون هذا مفيدًا للحصول على اسم أفضل مثلًا:
delegate :street, to: :address, prefix: true
المثال السابق يولّد address_street
بدلًا من street
.
تحذير: نظرًا لأنَّ اسم التابع المُولَّد في هذه الحالة يتكوّن من أسماء الكائن الهدف والتابع الهدف، فيجب أن يكون الخيار to:
اسم تابع.
يمكن أيضًا إعداد بادئة مُخصّصة:
delegate :size, to: :attachment, prefix: :avatar
في المثال السابق يُنشئ الماكرو avatar_size
بدلًا من size
.
ملاحظة: مُعرّف في active_support/core_ext/module/delegation.rb.
delegate_missing_to
فلنفترض أنك تريد تفويض (delegate) كل ما هو مفقود من الكائن User
إلى الكائن Profile
. يتيح لك الماكرو delegate_missing_to
تنفيذ ذلك بسهولة:
class User < ApplicationRecord
has_one :profile
delegate_missing_to :profile
end
يمكن أن يكون الهدف أي شيء قابل للنداء داخل الكائن، على سبيل المثال متغيرات النسخة (instance)، والتوابع، والثوابت (constants)، إلخ. تُفوّض توابع الهدف العامّة فقط.
ملاحظة: مُعرّف في active_support/core_ext/module/delegation.rb.
إعادة تعريف التوابع
أحيانًا هناك حالات تحتاج فيها إلى تعريف تابع مع define_method
ولكن لا تعرف إن كان هناك تابع آخر بهذا الاسم بالفعل. في حالة حدوث ذلك، سيُصدر تحذير إذا فٌعّلا. ليست مشكلة كبيرة لكنّها تظل مزعجة.
يمنع التابع redefine_method
مثل هذا التحذير المحتمل مع إزالة التابع الحالي مسبقًا إن لزم الأمر.
تستطيع أيضًا استخدام silence_redefinition_of_method
إن كنت بحاجة لتعريف التابع البديل بنفسك (لأنك تستخدم delegate
على سبيل المثال).
ملاحظة: مُعرّف في active_support/core_ext/module/redefine_method.rb.