ملحقات الدعم الفعال الأساسية في ريلز

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

الدعم الفعَّال (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

تُعتبر القيم التالية فارغة في تطبيق ريلز:

تنويه: تستخدم التوابع الخبرية (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/introspection.rb.

الوحدات المجهولة

قد أو قد لا تحتوي وحدةٌ (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.

ملحقات لصنف (Extensions to Class)

خاصيّات الصنف

التابع class_attribute

يُصرّح (declare) التابع class_attribute عن واحدة أو أكثر من خاصيّات الصنف القابلة للوراثة التي يمكن إعادة تعريفها (override) في أي مستوى بالتسلسل الهرمي.

class A
  class_attribute :x
end
 
class B < A; end
 
class C < B; end
 
A.x = :a
B.x # => :a
C.x # => :a
 
B.x = :b
A.x # => :a
C.x # => :b
 
C.x = :c
A.x # => :a
B.x # => :b

على سبيل المثال، يُعرّف ActionMailer::Base:

class_attribute :default_params
self.default_params = {
  mime_version: "1.0",
  charset: "UTF-8",
  content_type: "text/plain",
  parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze

كما يمكن الوصول إليها وإعادة تعريفها على مستوى النسخة (instance).

A.x = 1
 
a1 = A.new
a2 = A.new
a2.x = 2
 
a1.x # => 1, comes from A
a2.x # => 2, overridden in a2

يمكن منع توليد تابع نسخة الكاتب (writer instance) بضبط الخيار instance_writer: إلى القيمة false.

module ActiveRecord
  class Base
    class_attribute :table_name_prefix, instance_writer: false, default: "my"
  end
end

قد يجد نموذجٌ ما هذا الخيار مفيدًا كطريقة لمنع الإسناد الجماعي (mass-assignment) من تعيين الخاصيّة. يمكن منع توليد تابع نسخة القارئ (writer instance) بضبط الخيار instance_writer: إلى القيمة false.

class A
  class_attribute :x, instance_reader: false
end
 
A.new.x = 1
A.new.x # NoMethodError

للتبسيط، يُعرّف class_attribute أيضًا تابع نُسخة خبري (instance predicate) وهو النفي المزدوج لما يعيده نسخة القارئ. في الأمثلة المذكورة أعلاه سوف، أطلقنا عليه ?x.

عندما تكون قيمة الخيار instance_reader: القيمة false، يعيد تابع النسخة الخبري الخطأ NoMethodError مثل تابع القارئ تمامّا.

إن لم ترغب في استعمال تابع النسخة الخبري، مرّر instance_predicate: false ولن يُعرَّف آنذاك.

ملاحظة: مُعرّف في active_support/core_ext/class/attribute.rb.

التوابع cattr_reader، و cattr_writer، و cattr_accessor

تُماثل توابع الماكرو cattr_reader و cattr_writer و cattr_accessor نظيراتها *_attr ولكن للأصناف. ويهيؤون (initialize) متغيّر صنف بالقيمة nil إن لم يكن موجودًا بالفعل ويُولّدون تابع الصنف المناسب للوصول إليه:

class MysqlAdapter < AbstractAdapter
  # @@emulate_booleans توليد توابع صنف للوصول إلى
  cattr_accessor :emulate_booleans, default: true
end

تُنشأ أيضًا توابع النسخة للتبسيط التي هي مجرّد وكلاء (proxies) للخاصية class. لذلك، يمكن للنُسخ تغيير الخاصيّة class، لكن لا يمكنهم إعادة تعريفها كما يحدث مع class_attribute (انظر أعلاه). خذ هذا المثال:

module ActionView
  class Base
    cattr_accessor :field_error_proc, default: Proc.new { ... }
  end
end

يمكننا الوصول إلى field_error_proc في العروض (views).

class MysqlAdapter < AbstractAdapter
  # true مع القيمة الافتراضية @@emulate_booleans توليد توابع صنف للوصول إلى
  cattr_accessor :emulate_booleans, default: true
end

يمكن منع توليد تابع نسخة القارئ عبر ضبط instance_reader: إلى القيمة false ومنع توليد تابع نسخة الكاتب عبر ضبط instance_writer: إلى القيمة false. يمكن منع توليد كلا التابعين عبر ضبط instance_accessor: إلى القيمة false. يجب أن تكون القيمة false تحديدًا في جميع الحالات وليس أي قيمة خطأ تقيَّم إلى false.

module A
  class B
    # first_name لن تولد نسخة القارئ
    cattr_accessor :first_name, instance_reader: false
    # last_name= لن تولد نسخة الكاتب
    cattr_accessor :last_name, instance_writer: false
    # surname= أو نسخة الكاتبsurname لن تولد نسخة القارئ
    cattr_accessor :surname, instance_accessor: false
  end
end

قد يجد النموذج أنه من المفيد ضبط instance_accessor: إلى القيمة false كطريقة لمنع الإسناد الجماعي (mass-assignment) من تعيين الخاصيّة.

ملاحظة: مُعرّف في active_support/core_ext/module/attribute_accessors.rb.

الأصناف الفرعية والسليلة

التابع subclasses

يعيد التابع subclasses الأصناف الفرعية للمستقبل:

class C; end
C.subclasses # => []
 
class B < C; end
C.subclasses # => [B]
 
class A < B; end
C.subclasses # => [B]
 
class D < C; end
C.subclasses # => [B, D]

الترتيب الذي تعاد وفقه هذه الأصناف غير محدّد.

ملاحظة: مُعرّف في active_support/core_ext/class/subclasses.rb.

التابع descendants

يعيد التابع descendants جميع الأصناف المنحدرة (>) من المستقبل:

class C; end
C.descendants # => []
 
class B < C; end
C.descendants # => [B]
 
class A < B; end
C.descendants # => [B, A]
 
class D < C; end
C.descendants # => [B, A, D]

الترتيب الذي تعاد وفقه هذه الأصناف غير محدّد.

ملاحظة: مُعرّف في active_support/core_ext/class/subclasses.rb.

من ملحقات إلى سلاسل نصية (Extensions to String)

سلامة الخَرج (Output Safety)

الدافع (Motivation)

يحتاج إدخال البيانات إلى قوالب HTML عنايةً إضافيّة. لا تستطيع مثلًا حشر review.title@ حرفيًا بصفحة HTML. فلو كان العنوان مثلًا "!Flanagan & Matz rules"، لن تكون تركيبة الخَرج صحيحة لأنه من الضروري تهريب حرف العطف & بهذا الشكل ";amp&". زيادةً على ذلك، قد يكون هذا ثغرة أمنيّة كبيرة لأنه من الممكن للمُستخدمين حقن شيفرة HTML خبيثة تضع عنوانًا آخر للصفحة.

ألقِ نظرة على القسم "البرمجة عبر المواقع" في دليل تأمين تطبيقات ريلز لمزيد من المعلومات حول المخاطر.

السلاسل النصيّة الآمنة

يمتلك الدعم الفعَّال (Active Support) مفهوم السلاسل الآمنة (html). السلسلة الآمنة هي التي تُعلَّم على أنها قابلة للإدراج في شيفرة HTML كما هي، إذ هي موثوقة بغض النظر عما إذا كانت مُهرّبة أم لا.

تعتبر السلاسل النصيّة غير آمنة افتراضيًّا:

"".html_safe? # => false

تستطيع الحصول على سلسلة آمنة من أخرى عاديّة باستعمال التابع html_safe:

s = "".html_safe
s.html_safe? # => true

من المهم أن نفهم أن html_safe لا يُهرّب أي شيء على الإطلاق بل هو مجرد تأكيد:

s = "<script>...</script>".html_safe
s.html_safe? # => true
s            # => "<script>...</script>"

تقع على عاتقك مسؤولية التأكد من أن استدعاء html_safe على سلسلة معيّنة أمر جيّد ولا يحمل أية مخاطر. إن ألحقت (append) شيئًا بسلسلة آمنة إمّا بمكان مُحدّد مع >>/concat أو مع +، فإن النتيجة هي سلسلة آمنة. تُهرّب الوسائط غير الآمنة:

"".html_safe + "<" # => "&lt;"

تُلحق الوسائط الآمنة مباشرة:

"".html_safe + "<".html_safe # => "<"

يجب عدم استخدام هذه التوابع في العروض العادية. تُهرّب القيم غير الآمنة تلقائيًا:

<%= @review.title %> <%# fine, escaped if needed %>

لإدراج شيء ما حرفيًا، استخدم المُساعد raw بدل استدعاء html_safe:

<%= raw @cms.current_template %> <%# inserts @cms.current_template as is %>

أو بطريقة أخرى مكافئة، استخدم ‎<%==‎:

<%== @cms.current_template %> <%# inserts @cms.current_template as is %>

يستدعي المُساعد raw التابع html_safe بدلًا منك:

def raw(stringish)
  stringish.to_s.html_safe
end

ملاحظة: مُعرّف في active_support/core_ext/string/output_safety.rb.

التحوّل (Transformation)

كقاعدة عامة وباستثناء عملية الدمج (concatenation) كما هو موضّح أعلاه، أي تابع يستطيع تغيير سلسلة نصيّة يعطيك سلسلة غير آمنة. وهم downcase، و gsub، و strip، و chomp، و underscore ...إلخ.

في حالة التحوّلات في نفس المكان مثل !gsub، يصبح المستقبل نفسه غير آمن.

تنويه: يُفقد بت (bit) السلامة دائمًا بغض النظر عن تغيير التحويل لأي شيء أم لا.

التحويل والتحويل بالإكراه (Conversion and Coercion)

التحويل باستدعاء to_s على سلسلة نصيّة آمنة يعيد سلسلة نصيّة آمنة ولكن التحويل بالإكراه عبر التابع to_str يعيد سلسلة غير آمنة.

النسخ

استدعاء dup أو clone على سلاسل آمنة ينتج سلاسل آمنة.

التابع remove

يحذف التابع remove جميع تكرارات النمط:

"Hello World".remove(/Hello /) # => "World"

توجد أيضًا النسخة المدمرة من هذا التابع هي !String.remove.

ملاحظة: مُعرّف في active_support/core_ext/string/filters.rb.

التابع squish

يزيل التابع squish المسافات البيضاء الزائدة السابقة واللاحقة ويستبدل المسافات البيضاء بين الكلمات بفراغ واحدة:

" \n  foo\n\r \t bar \n".squish # => "foo bar"

توجد أيضًا نسخة مدمرة هي !String.squish.

لاحظ أنه يُعالج كل من مسافات ASCII و Unicode البيضاء.

ملاحظة: مُعرّف في active_support/core_ext/string/filters.rb.

التابع truncate

يعيد التابع truncate نسخة (copy) مقطوعة (truncated) من مستقبلها بعد طول معيّن (يحدد بالمعامل length):

"Oh dear! Oh dear! I shall be late!".truncate(20)
# => "Oh dear! Oh dear!..."

يمكن تخصيص النقط الثلاث (Ellipsis) التي توضع بدل الكلام المقتطع باستخدام الخيار omission::

"Oh dear! Oh dear! I shall be late!".truncate(20, omission: '&hellip;')
# => "Oh dear! Oh &hellip;"

لاحظ خاصّة أنَّ الاقتطاع يأخذ طول سلسلة الحذف (omission string) في الحُسبان. مرّر الخيار separator: لاقتطاع السلسلة في فاصل طبيعي:

"Oh dear! Oh dear! I shall be late!".truncate(18)
# => "Oh dear! Oh dea..."
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ')
# => "Oh dear! Oh..."

يمكن للخيار separator: أن يكون تعبيرًا نمطيًّا:

"Oh dear! Oh dear! I shall be late!".truncate(18, separator: /\s/)
# => "Oh dear! Oh..."

في الأمثلة أعلاه تُقطع "dear" في البداية ثم يمنع separator: ذلك.

ملاحظة: مُعرّف في active_support/core_ext/string/filters.rb.

التابع truncate_words

يعيد التابع truncate_words نسخة من مستقبله بعد اقتطاعه (truncated) عند عدد معيّن من الكلمات:

"Oh dear! Oh dear! I shall be late!".truncate_words(4)
# => "Oh dear! Oh dear!..."

يمكن تخصيص النقط الثلاث (Ellipsis) التي توضع بدل الكلام المقتطع باستخدام الخيار omission::

"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: '&hellip;')
# => "Oh dear! Oh dear!&hellip;"

مرّر الخيار separator: لاقتطاع السلسلة عند فاصل طبيعي:

"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!')
# => "Oh dear! Oh dear! I shall be late..."

يمكن للخيار separator: أن يكون تعبيرًا نمطيًّا:

"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/)
# => "Oh dear! Oh dear!..."

ملاحظة: مُعرّف في active_support/core_ext/string/filters.rb.

التابع Inquiry

يُحوّل التابع inquiry سلسلة نصيّة إلى كائن من النوع StringInquirer مما يجعل التحقّق من المساواة أجمل.

production".inquiry.production? # => true
"active".inquiry.inactive?       # => false

التابعان ?starts_with و ?ends_with

يعرّف الدعم الفعال أسماء ضمير الغائب البديلة من ?String.start_with و ?String#end_with:

"foo".starts_with?("f") # => true
"foo".ends_with?("o")   # => true

ملاحظة: مُعرّف في active_support/core_ext/string/starts_ends_with.rb.

التابع strip_heredoc

يزيل التابع strip_heredoc المسافات البادئة (indentation) في الصيغة heredoc.

مثلًا:

if options[:usage]
  puts <<-USAGE.strip_heredoc
    This command does such and such.
 
    Supported options are:
      -h         This message
      ...
  USAGE
end

سيرى المستخدم رسالة الاستخدام حذو الهامش الأيسر.

تقنيًّا، يبحث التابع عن الخط الأقل إزاحة (least indented) في السلسلة بأكملها ويزيل ذاك المقدار من المسافة البيضاء الزائدة.

ملاحظة: مُعرّف في active_support/core_ext/string/strip.rb.

التابع Indent

يضيف مسافة بادئة للأسطر في المستقبل:

<<EOS.indent(2)
def some_method
  some_code
end
EOS
# =>
  def some_method
    some_code
  end

يحدد الوسيط الثاني indent_string أي مسافة بادئة يُراد استخدامها. القيمة الافتراضيّة هي nil وهي تُخبر التابع أن يُخمّن عبر النظر في أوّل السطر مُزاح بمسافة بادئة والرجوع بمسافة فارغة إن لم يوجد.

"  foo".indent(2)        # => "    foo"
"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
"foo".indent(2, "\t")    # => "\t\tfoo"

بينما يكون indent_string عادةً مسافة واحدة أو مسافة جدولة (tab)، يظل من الممكن أن تكون أي سلسلة. الوسيط الثالث indent_empty_lines هي علامة تشير إلى ما إذا وجب وضع مسافات بادئة للسطور الفارغة. قيمتها الافتراضيّة هي false.

"foo\n\nbar".indent(2)            # => "  foo\n\n  bar"
"foo\n\nbar".indent(2, nil, true) # => "  foo\n  \n  bar"

يضع التابع !indent المسافة البادئة في في السلسلة نفسها عبر تعديل المستقبل.

ملاحظة: مُعرّف في active_support/core_ext/string/indent.rb.

الوصول (Access)

التابع at(position)‎

يعيد محرفًا ذا موضع محدَّد (في الموضع position) في السلسلة النصية:

"hello".at(0)  # => "h"
"hello".at(4)  # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil

ملاحظة: مُعرّف في active_support/core_ext/string/access.rb.

التابع from(position)‎

يعيد السلسلة الفرعية من السلسلة النصية المعطاة التي تبدأ من الموضع position:

"hello".from(0)  # => "hello"
"hello".from(2)  # => "llo"
"hello".from(-2) # => "lo"
"hello".from(10) # => nil

ملاحظة: مُعرّف في active_support/core_ext/string/access.rb.

التابع to(position)‎

يعيد السلسلة الفرعية للسلسلة النصية المعطاة حتى الموضع position:

"hello".to(0)  # => "h"
"hello".to(2)  # => "hel"
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"

ملاحظة: مُعرّف في active_support/core_ext/string/access.rb.

التابع first(limit = 1)‎

يُماثل استدعاء التابع str.first(n)‎ الاستدعاء str.to(n-1)‎ إن كانت n > 0 ويعيد سلسلة فارغة عندما تكون n == 0.

ملاحظة: مُعرّف في active_support/core_ext/string/access.rb.

التابع last(limit = 1)‎

يُماثل استدعاء التابع str.last(n)‎ الاستدعاء str.last(n)‎ إن كانت n > 0 ويعيد سلسلة فارغة لمّا n == 0.

ملاحظة: مُعرّف في active_support/core_ext/string/access.rb.

المشتقات (Inflections)

التابع pluralize

يعيد التابع pluralize صيغة جمع (plural) للمستقبل:

"table".pluralize     # => "tables"
"ruby".pluralize      # => "rubies"
"equipment".pluralize # => "equipment"

كما ترى، يعرف الدعم الفعَّال بعض صيغ الجمع الشاذة (irregular plurals) والأسماء غير المعدودة (uncountable nouns). يمكن توسيع القواعد والمفردات في config/initializers/inflections.rb. يُنشَأ هذا الملف بواسطة الأمر rails وبه إرشادات في التعليقات. يستطيع pluralize أيضًا أن يأخذ معاملًا (parameter) اختياريًا هو count. إن كان count == 1، فستُعاد الصيغة المفردة. بالنسبة لأية قيمة أخرى للمعامل count، ستعاد صيغة الجمع:

"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"

يستخدم السجل الفعال هذه التابع لحساب اسم الجدول الافتراضي الذي يوافق النموذج:

# active_record/model_schema.rb
def undecorated_table_name(class_name = base_class.name)
  table_name = class_name.to_s.demodulize.underscore
  pluralize_table_names ? table_name.pluralize : table_name
end

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع singularize

يسلك هذا التابع سلوكًا معاكسًا للتابع pluralize:

tables".singularize    # => "table"
"rubies".singularize    # => "ruby"
"equipment".singularize # => "equipment"

تحسب الارتباطات (Associations) اسم الصنف الافتراضي المرتبط باستخدام هذا التابع:

# active_record/reflection.rb
def derive_class_name
  class_name = name.to_s.camelize
  class_name = class_name.singularize if collection?
  class_name
end

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع camelize

يعيد التابع camelize المستقبل في نمط سنام الجمل (CamelCase):

"product".camelize    # => "Product"
"admin_user".camelize # => "AdminUser"

تستطيع التفكير في هذه التابع باعتباره التابع التي يحوّل المسارات (paths) إلى صنف روبي أو أسماء وحدات (module) حيث تقوم الشرطات المائلة (slashes) بفصل مجالات الأسماء:

"backoffice/session".camelize # => "Backoffice::Session"

يستخدم الإجراء Pack مثلًا هذا التابع لتحميل الصنف الذي يوفّر مخزن جلسة معيّن:

# action_controller/metal/session_management.rb
def session_store=(store)
  @@session_store = store.is_a?(Symbol) ?
    ActionDispatch::Session.const_get(store.to_s.camelize) :
    store
end

يأخذ التابع camelize وسيطًا اختياريًّا يمكن أن يكون upper: (الافتراضي) أو lower:. مع القيمة الأخيرة للوسيط، يُصبح الحرف الأول صغيرًا:

"visual_effect".camelize(:lower) # => "visualEffect"

قد يكون هذا مفيدًا لحساب أسماء التابع باللغات التي تتبع هذا العرف مثل لغة جافاسكربت.

تنويه: تستطيع عمومًا التفكير في camelize كعكس underscore على الرغم من وجود حالات تخالف ذلك:

"SSLError".underscore.camelize يعيد "SslError". لدعم  مثل هذه حالات، يسمح لك الدعم الفعَّال بتحديد الاختصارات (acronyms) في config/initializers/inflections.rb:

ActiveSupport::Inflector.inflections do |inflect|
  inflect.acronym 'SSL'
end
 
"SSLError".underscore.camelize # => "SSLError"

التابع camelcase هو اسم بديل للتابع camelize.

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع underscore

يعمل التابع underscore عكس التابع camelcase إذ يأخذ سلسلة مكتوبة بنمط سنام الجمل ويحولها إلى مسار ويفصل الكلمات بشرطة سفلية:

"Product".underscore   # => "product"
"AdminUser".underscore # => "admin_user"

كما يحوّل "::" أيضًا إلى "/":

"Backoffice::Session".underscore # => "backoffice/session"

ويفهم السلاسل النصيّة التي تبدأ بالحروف الصغيرة:

"visualEffect".underscore # => "visual_effect"

لا يقبل underscore أي وسيط رغم ذلك. يستخدم التحميل التلقائي لأصناف ريلز ووحداته التابع underscore للاستدلال على المسار النسبي بدون امتداد ملف (file extension) والذي من شأنه تحديد ثابت مفقود محدّد:

# active_support/dependencies.rb
def load_missing_constant(from_mod, const_name)
  ...
  qualified_name = qualified_name_for from_mod, const_name
  path_suffix = qualified_name.underscore
  ...
end

تستطيع افتراض أنَّ التابع underscore كعكس camelize كقاعدة عامة على الرغم من وجود حالات مخالفة لذلك. مثلًا "SSLError".underscore.camelize يعيد "SslError".

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع titleize

يحوّل التابع titleize الحروف لحروف كبيرة في المستقبل:

"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize     # => "Fermat's Enigma"

التابع titlecase هو اسم بديل للتابع titleize.

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع dasherize

يبدل التابع dasherize الشرطات العادية في المستقبل مكان الشرطات السفلية:

"name".dasherize         # => "name"
"contact_data".dasherize # => "contact-data"

يستخدم مُسَلسِل XML للنماذج (XML serializer of models) هذا التابع لتطبيقه على أسماء العُقد (node names):

# active_model/serializers/xml.rb
def reformat_name(name)
  name = name.camelize if camelize?
  dasherize? ? name.dasherize : name
end

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع demodulize

في حالة الحصول على سلسلة ذات اسم ثابت مُميّز (qualified constant name)، يعيد demodulize اسم الثابت تحديدًا، أي جزئه الأيمن:

"Product".demodulize                        # => "Product"
"Backoffice::UsersController".demodulize    # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize                  # => "Inflections"
"".demodulize                               # => ""

يستخدم السجل الفعال مثلًا هذا التابع لحساب اسم حقل عدّاد مؤقت (counter cache column):

# active_record/reflection.rb
def counter_cache_column
  if options[:counter_cache] == true
    "#{active_record.name.demodulize.underscore.pluralize}_count"
  elsif options[:counter_cache]
    options[:counter_cache]
  end
end

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع deconstantize

في حالة الحصول على سلسلة ذات اسم ثابت مُميّز (qualified constant)، يحذف التابع deconstantize جزئه الأيمن تاركًا، عادةً، اسم حاوي الثابت:

"Product".deconstantize                        # => ""
"Backoffice::UsersController".deconstantize    # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع parameterize

يُطبّع التابع parameterize المستقبل بحيث يمكن استخدامه في عناوين URL جميلة.

"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"

للحفاظ على حالة السلسلة، اضبط الوسيط preserve_case إلى القيمة true. قيمة preserve_case هي false افتراضيًّا.

"John Smith".parameterize(preserve_case: true) # => "John-Smith"
"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel"

أعد تعريف (override) الوسيط separator لاستخدام فاصل مخصّص.

"John Smith".parameterize(separator: "_") # => "john\_smith"
"Kurt Gödel".parameterize(separator: "_") # => "kurt\_godel"

تُلَف السلسلة النصية الناتجة في نسخة من ActiveSupport::Multibyte::Chars.

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع tableize

التابع tableize هو underscore يليه pluralize.

"Person".tableize      # => "people"
"Invoice".tableize     # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"

يعيد tableize اسم الجدول الذي يوافق نموذجًا محددُّا للحالات البسيطة. التطبيق الفعلي داخل السجل الفعال ليس tableize مباشرةً بالفعل، لأنه يُجرّد أيضًا اسم الصنف ويفحص بعض الخيارات التي قد تؤثر على السلسلة المعادة.

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع classify

التابع classify هو عكس tableize، إذ يعطيك اسم الصنف الموافق لاسم الجدول:

"people".classify        # => "Person"
"invoices".classify      # => "Invoice"
"invoice_lines".classify # => "InvoiceLine"

يفهم التابع أسماء الجداول المُميّزة:

"highrise_production.companies".classify # => "Company"

لاحظ أن classify يعيد اسم صنف كسلسلة نصية. تستطيع الحصول على كائن الصنف الفعلي باستدعاء constantize عليه، كما سيوضّح لاحقًا.

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع constantize

يستبين (resolves) التابع constantize تعبير مرجع الثابت (constant reference expression) في المستقبل:

"Integer".constantize # => Integer
 
module M
  X = 1
end
"M::X".constantize # => 1

يرفع constantize الخطأ NameError إن قُيِّمَت السلسلة النصيّة إلى ثابت غير معروف أو إن لم يكن محتواها اسم ثابت صالح. يبدأ استبيان اسم الثابت بواسطة constantize دائمًا في الكائن Object ذي المستوى الأعلى حتى إن لم توجد البادئة "::".

X = :in_Object
module M
  X = :in_M
 
  X                 # => :in_M
  "::X".constantize # => :in_Object
  "X".constantize   # => :in_Object (!)
end

لذلك، لا تعادل هذه العمليّة بشكل عام ما كان ليفعله روبي في نفس الموقف لو قُيّم ثابت حقيقي. تحصل حالات اختبار Mailer على مُرسل البريد تحت الاختبار من اسم صنف الاختبار باستخدام constantize:

# action_mailer/test_case.rb
def determine_default_mailer(name)
  name.sub(/Test$/, '').constantize
rescue NameError => e
  raise NonInferrableMailerError.new(name)
end

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع humanize

يُعدّل التابع humanize اسم خاصيّة لعرضها للمستخدمين النهائيين. وتحديدًا ينفّذ هذه التحويلات:

  • تطبيق قواعد تصريف بشرية على الوسيط.
  • حذف الشرطات السفلية الأماميّة إن وجدت.
  • إزالة اللاحقة "‎_id" إذا كانت موجودة.
  • استبدال الشرطات السفلية بمسافات فارغة إن وجدت.
  • يُصغّر كل الحروف ما عدا المختصرات (acronyms).
  • تكبير حروف أوّل كلمة.

يمكن إيقاف تكبير حروف الكلمة الأولى عبر ضبط الخيار capitalize: إلى القيمة false (القيمة الافتراضية هي true).

"name".humanize                         # => "Name"
"author_id".humanize                    # => "Author"
"author_id".humanize(capitalize: false) # => "author"
"comments_count".humanize               # => "Comments count"
"_id".humanize                          # => "Id"

إذا عُرفّت "SSL" كاختصار:

'ssl_error'.humanize # => "SSL error"

يستخدم التابع المساعد full_messages التابع humanize كخطّة احتياطيّة لتضمين أسماء الخاصيّات:

def full_messages
  map { |attribute, message| full_message(attribute, message) }
end
 
def full_message
  ...
  attr_name = attribute.to_s.tr('.', '_').humanize
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
  ...
end

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التابع foreign_key

يعطي التابع foreign_key اسم حقل مفتاح خارجي (foreign key column name) من اسم صنف. لذلك، يحذف الوحدة (demodulizes) ويضيف شرطة سفليّة (underscores) ويضيف "‎_id":

"User".foreign_key           # => "user_id"
"InvoiceLine".foreign_key    # => "invoice_line_id"
"Admin::Session".foreign_key # => "session_id"

مرّر الوسيط false إن لم تُرد شرطة سفليّة في "id_":

"User".foreign_key(false) # => "userid"

تستخدم الارتباطات (Association) هذا التابع لاستنتاج المفاتيح الخارجية، إذ يفعل الارتباط has_one والارتباط has_many مثلًا هذا:

# active_record/associations.rb
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key

ملاحظة: مُعرّف في active_support/core_ext/string/inflections.rb.

التحويلات (Conversions)

التوابع to_date، و to_time، و to_datetime

تُعدّ التوابع to_date و to_time و to_datetime كأغلفة ملائمة حول Date._parse:

"2010-07-27".to_date              # => Tue, 27 Jul 2010
"2010-07-27 23:37:00".to_time     # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000

يتلقّى to_time وسيطًا اختياريًّا هو utc: أو local: للإشارة إلى المنطقة الزمنية التي تريد التوقيت حسبها:

"2010-07-27 23:42:00".to_time(:utc)   # => 2010-07-27 23:42:00 UTC
"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200

الوسيط الافتراضي هو local:.

يرجى الرجوع إلى توثيق Date._parse لمزيد من التفاصيل.

تنويه: تعيد التوابع الثلاثة القيمة nil في حالة كون المستقبل فارغًا.

ملاحظة: مُعرّف في active_support/core_ext/string/conversions.rb.

من ملحقات إلى أعداد (Extensions to Numeric)

البايتات (Bytes)

تستجيب جميع الأرقام لهذه التوابع:

bytes
kilobytes
megabytes
gigabytes
terabytes
petabytes
exabytes

يُرجعون الكمية المقابلة من البايتات باستخدام عامل تحويل يبلغ 1024:

2.kilobytes   # => 2048
3.megabytes   # => 3145728
3.5.gigabytes # => 3758096384
-4.exabytes   # => -4611686018427387904

تتخذ الصيغ الفردية أسماءً بديلة، لذلك تستطيع قول:

1.megabyte # => 1048576

ملاحظة: مُعرّف في active_support/core_ext/numeric/bytes.rb.

الوقت

يُمكّن Time من استخدام حسابات الوقت والتصريحات، مثل ‎45.minutes + 2.hours + 4.weeks.

تستخدم هذه التوابع Time.advance لحساب التواريخ بدقّة عند استخدام from_now، و ago ...إلخ. بالإضافة إلى إضافة أو طرح نتائجها من كائن وقت Time. مثلًا:

# Time.current.advance(months: 1) يكافئ
1.month.from_now
 
# Time.current.advance(weeks: 2) يكافئ
2.weeks.from_now
 
# Time.current.advance(months: 4, weeks: 5) يكافئ
(4.months + 5.weeks).from_now

تحذير: لفترات وأوقات أخرى، يرجى الاطلاع على قسم ملحقات العدد الصحيح في الأسفل.

ملاحظة: مُعرّف في active_support/core_ext/numeric/time.rb.

التنسيق

يُمكّن من تنسيق الأرقام بطرق متنوعة منها:

  • إنتاج سلسلة نصيّة تمثِّل رقمًا كرقم هاتف:
5551234.to_s(:phone)
# => 555-1234
1235551234.to_s(:phone)
# => 123-555-1234
1235551234.to_s(:phone, area_code: true)
# => (123) 555-1234
1235551234.to_s(:phone, delimiter: " ")
# => 123 555 1234
1235551234.to_s(:phone, area_code: true, extension: 555)
# => (123) 555-1234 x 555
1235551234.to_s(:phone, country_code: 1)
# => +1-123-555-1234
  • إنتاج سلسلة نصية تمثِّل رقمًا كعملة:
1234567890.50.to_s(:currency)                 # => $1,234,567,890.50
1234567890.506.to_s(:currency)                # => $1,234,567,890.51
1234567890.506.to_s(:currency, precision: 3)  # => $1,234,567,890.506
  • إنتاج سلسلة نصية تمثِّل رقمًا كنسبة مئوية:
100.to_s(:percentage)
# => 100.000%
100.to_s(:percentage, precision: 0)
# => 100%
1000.to_s(:percentage, delimiter: '.', separator: ',')
# => 1.000,000%
302.24398923423.to_s(:percentage, precision: 5)
# => 302.24399%
  • إنتاج سلسلة نصية تمثِّل رقمًا في شكل محدّد (delimited form):
12345678.to_s(:delimited)                     # => 12,345,678
12345678.05.to_s(:delimited)                  # => 12,345,678.05
12345678.to_s(:delimited, delimiter: ".")     # => 12.345.678
12345678.to_s(:delimited, delimiter: ",")     # => 12,345,678
12345678.05.to_s(:delimited, separator: " ")  # => 12,345,678 05
  • إنتاج سلسلة نصية تمثِّل عددًا مقربًا (number rounded) إلى دقة محدَّدة:
111.2345.to_s(:rounded)                     # => 111.235
111.2345.to_s(:rounded, precision: 2)       # => 111.23
13.to_s(:rounded, precision: 5)             # => 13.00000
389.32314.to_s(:rounded, precision: 0)      # => 389
111.2345.to_s(:rounded, significant: true)  # => 111
  • إنتاج سلسلة نصية تمثِّل رقمًا كرقم يمكن قرائته بسهولة:
123.to_s(:human_size)                  # => 123 Bytes
1234.to_s(:human_size)                 # => 1.21 KB
12345.to_s(:human_size)                # => 12.1 KB
1234567.to_s(:human_size)              # => 1.18 MB
1234567890.to_s(:human_size)           # => 1.15 GB
1234567890123.to_s(:human_size)        # => 1.12 TB
1234567890123456.to_s(:human_size)     # => 1.1 PB
1234567890123456789.to_s(:human_size)  # => 1.07 EB
  • إنتاج سلسلة نصيّة تمثِّل رقمًا في كلمات يمكن للإنسان قراءتها:
123.to_s(:human)               # => "123"
1234.to_s(:human)              # => "1.23 Thousand"
12345.to_s(:human)             # => "12.3 Thousand"
1234567.to_s(:human)           # => "1.23 Million"
1234567890.to_s(:human)        # => "1.23 Billion"
1234567890123.to_s(:human)     # => "1.23 Trillion"
1234567890123456.to_s(:human)  # => "1.23 Quadrillion"

ملاحظة: معرّف في active_support/core_ext/numeric/conversions.rb.

مصادر