الفرق بين المراجعتين لصفحة: «Rails/autoloading and reloading constants»

من موسوعة حسوب
لا ملخص تعديل
طلا ملخص تعديل
 
(3 مراجعات متوسطة بواسطة نفس المستخدم غير معروضة)
سطر 308: سطر 308:
</syntaxhighlight>
</syntaxhighlight>


'''تنبيه''': يحسب <code>autoload_paths</code> ويخزن مؤقتًا أثناء عملية التهيئة. يجب إعادة تشغيل التطبيق لتطبيق أي تغييرات في بنية المجلد.
'''تنبيه''': يُحسَب <code>autoload_paths</code> ويُخزَّن مؤقتًا أثناء عملية التهيئة. يجب إعادة تشغيل التطبيق لتطبيق أي تغييرات في بنية المجلد.


== خوارزميات التحميل التلقائي ==
== خوارزميات التحميل التلقائي ==


=== المراجع النسبية ===
=== المراجع النسبية ===
قد يظهر مرجع ثابت نسبي في عدة أماكن، على سبيل المثال، في
قد يظهر مرجع ثابت نسبي في عدة أماكن، على سبيل المثال، في:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
class PostsController < ApplicationController
class PostsController < ApplicationController
 
  def index
 def index
    @posts = Post.all
 
  end
   @posts = Post.all
end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


كل المراجع الثلاثة الثابتة نسبية.
كل المراجع الثلاثة الثابتة نسبية.


==== الثوابت بعد الكلمات الأساسية class و module ====
==== الثوابت بعد الكلمتين المفتاحيتين <code>class</code> و <code>module</code> ====
ينفّذ روبي بحثًا عن الثابت الذي يتبع الكلمة المفتاحية class او module لأنه يحتاج إلى معرفة ما إذا كان سينشأ الصنف أو الوحدة النمطية أو سيُعيد فتحها.
ينفّذ روبي بحثًا عن الثابت الذي يتبع الكلمة المفتاحية <code>class</code> أو <code>module</code> لأنه يحتاج إلى معرفة ما إذا كان سينشأ الصنف أو الوحدة أو سيُعياد فتحها.


إذا لم يُعرف الثابت عند هذه النقطة لا يعتبر ثابت مفقود، لا يُثار التحميل التلقائي.
إذا لم يُعرف الثابت عند هذه النقطة، لا يعتبر ثابت مفقودًا ولن يُنفَّذ التحميل التلقائي.


لذا، في المثال السابق، إذا لم يحدد الوظيفة PostController عند ترجمة الملف، لن يُشغل Rails autoloading، سيعمل Ruby على تعريف وحدة التحكم فقط.
لذا، في المثال السابق، إذا لم يُعرَّف <code>PostController</code> عند ترجمة الملف، لن يُشغل ريلز التحميل التلقائي، سيعمل روبي على تعريف المتحكم فقط.


==== ثوابت المستوى الأعلى ====
==== ثوابت المستوى الأعلى ====
على العكس من ذلك، إذا كان ApplicationController غير معروف، فسيعتبر الثابت مفقودًا وسيقوم Rails بمحاولة تحميل تلقائية.
على العكس من ذلك، إذا كان <code>ApplicationController</code> غير معروف، فسيعتبر الثابت مفقودًا وسيقوم ريلز بمحاولة التحميل التلقائي.


لتحميل ApplicationController، يتكرر Rails على autoload_paths. أولاً تحقق من وجود app/assets/application_controller.rb. إذا لم يحدث ذلك، وهو ما يحدث عادةً، فسيستمر في البحث عن app/controllers/application_controller.rb.
لتحميل <code>ApplicationController</code>، يُكرِّر ريلز عبر المسارات <code>autoload_paths</code>. أولًا تتحقق من وجود app/assets/application_controller.rb. إذا لم تعثر عليه، وهو ما يحدث عادةً، فسيستمر في البحث عن app/controllers/application_controller.rb.


إذا كان الملف يعرّف التطبيق الثابت، فإن كل شيء على ما يرام، وإلا فسيرفع LoadError:
إذا كان الملف يعرّف <code>ApplicationController</code> الثابت، فإن كل شيء على ما يرام، وإلا فسيرمى الاستثناء <code>[[Ruby/LoadError|LoadError]]</code>:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
unable to autoload constant ApplicationController, expected
unable to autoload constant ApplicationController, expected
<full path to application_controller.rb> to define it (LoadError)
<full path to application_controller.rb> to define it (LoadError)
</syntaxhighlight>
</syntaxhighlight>


ملاحظة: لا يتطلب Rails قيمة الثوابت التي تُحمل تلقائيًا لتكون صنف أو كائن وحدة نمطية. على سبيل المثال، إذا كان تطبيق الملف / models / max_clients.rb يعرّف MAX_CLIENTS = 100 autoloading MAX_CLIENTS يعمل بشكل جيد.
'''تنبيه''': لا يتطلب ريلز قيمة الثوابت التي تُحمل تلقائيًا لتكون كائن صنف أو وحدة. على سبيل المثال، إذا كان تطبيق الملف app/models/max_clients.rb يعرّف <code>MAX_CLIENTS = 100</code>، فإن التحميل التلقائي للثابت <code>MAX_CLIENTS</code> يعمل بشكل جيد.


==== مجالات الأسماء ====
==== مجالات الأسماء ====
يبدو التحميل التلقائي لـ ApplicationController مباشرة تحت دلائل autoload_paths لأن التداخل في تلك البقعة فارغ.
يبدو التحميل التلقائي للمتحكم <code>ApplicationController</code> مباشرة تحت مجلدات <code>autoload_paths</code> لأن التداخل في تلك البقعة فارغ. يختلف وضع <code>Post</code>، ويكون التشعب في هذا السطر هو <code>[PostsController]</code> ويأتي دور الدعم لمجالات الاسم.
 
يختلف وضع Post، ويكون التشعب في هذا الخط هو [PostsController] ويأتي تشغيل الدعم لمساحات الإسم.
 
الفكرة الأساسية هي أن تعطى


الفكرة الأساسية هي أن إعطاء:<syntaxhighlight lang="rails">
module Admin
module Admin
 
  class BaseController < ApplicationController
 class BaseController < ApplicationController
    @@all_roles = Role.all
 
  end
   @@all_roles = Role.all
end
 
</syntaxhighlight>للتحميل التلقائي لـ <code>Role</code> الذي سيفحص إذا تعرف في مجالات الأسماء الحالية أو المحلية، في وقت واحد. لذا، من الناحية النظرية، نرغب في محاولة التحميل التلقائي لأي منها:
 end
 
End
 
للتحميل التلقائي لـ Role الذي سيفحص إذا تعرف في مجالات الأسماء الحالية أو المحلية، في وقت واحد. لذا، من الناحية المفاهيمية، نرغب في محاولة التحميل التلقائي لأي منها
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
Admin::BaseController::Role
Admin::BaseController::Role
Admin::Role
Admin::Role
Role
Role
</syntaxhighlight>
</syntaxhighlight>


بهذا الترتيب. هذه هي الفكرة. للقيام بذلك، يبحث Rails في autoload_paths على التوالي لأسماء الملفات مثل هذه:
بهذا الترتيب. هذه هي الفكرة. للقيام بذلك، يبحث ريلز في <code>autoload_paths</code> على التوالي لأسماء الملفات مثل هذه:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="text">
admin/base_controller/role.rb
admin/base_controller/role.rb
admin/role.rb
admin/role.rb
Role.rb
Role.rb
</syntaxhighlight>
</syntaxhighlight>


بعض عمليات البحث عن الأدلة الإضافية التي سنغطيها قريبًا.
سنغطي قريبًا بعض عمليات البحث عن المجلدات الإضافية.


'Constant::Name'.underscore يعطي المسار النسبي بدون امتداد لاسم الملف حيث من المتوقع أن يُحدد Constant :: Name.
'''ملاحظة''': يعطي <code>'Constant::Name'.underscore</code> المسار النسبي بدون لاحقة لاسم الملف حيث من المتوقع أن يُحدد <code>Constant::Name</code>.


دعنا نرى كيف يحمل Rails تلقائيًا الثابت Post في postsController أعلاه بافتراض أن التطبيق يحتوي على نموذج Post محدد في app/models/post.rb.
دعنا نرى كيف يحمل ريلز تلقائيًا الثابت <code>Post</code> في <code>postsController</code> أعلاه بافتراض أن التطبيق يحتوي على النموذج <code>Post</code> المحدد في app/models/post.rb.


أولاً يتحقق من posts_controller / post.rb في autoload_paths:
أولاً يتحقق من posts_controller/post.rb في <code>autoload_paths</code>:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
app/assets/posts_controller/post.rb
app/assets/posts_controller/post.rb
app/controllers/posts_controller/post.rb
app/controllers/posts_controller/post.rb
app/helpers/posts_controller/post.rb
app/helpers/posts_controller/post.rb
...
...
test/mailers/previews/posts_controller/post.rb
test/mailers/previews/posts_controller/post.rb
</syntaxhighlight>
</syntaxhighlight>


نظرًا لاستنفاد البحث دون نجاح، يجري بحث مشابه عن الدليل، سنرى السبب في القسم التالي:
نظرًا لانتهاء البحث دون نجاح، يُجرَى بحث مشابه عن المجلد، وسنرى السبب في القسم التالي:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
app/assets/posts_controller/post
app/assets/posts_controller/post
app/controllers/posts_controller/post
app/controllers/posts_controller/post
app/helpers/posts_controller/post
app/helpers/posts_controller/post
...
...
test/mailers/previews/posts_controller/post
test/mailers/previews/posts_controller/post
</syntaxhighlight>
</syntaxhighlight>


إذا فشلت كل هذه المحاولات، فسيبدأ Rails عملية البحث مرة أخرى في مجال الاسم الأصل. في هذه الحالة، يبقى المستوى الأعلى فقط:
إذا فشلت كل هذه المحاولات، فسيبدأ ريلز عملية البحث مرة أخرى في مجال الاسم الأصل. في هذه الحالة، يبقى المستوى الأعلى فقط:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
app/assets/post.rb
app/assets/post.rb
app/controllers/post.rb
app/controllers/post.rb
app/helpers/post.rb
app/helpers/post.rb
app/mailers/post.rb
app/mailers/post.rb
app/models/post.rb
app/models/post.rb
</syntaxhighlight>
</syntaxhighlight>


إذا عُثر على ملف مطابق في app/models/post.rb. يتوقف البحث هناك ويُحمل الملف. إذا كان الملف يعرف بالفعل Post اذا كل شيء على ما يرام، وإلا يرفع LoadError.
إذا عُثر على ملف مطابق في app/models/post.rb. يتوقف البحث هناك ويُحمَّل الملف. إذا كان الملف يُعرِّف بالفعل <code>Post</code>، اذا كل شيء على ما يرام، وإلا سيرمى الاستثناء <code>[[Ruby/LoadError|LoadError]]</code>.


=== مراجع مؤهلة ===
=== مراجع مؤهلة ===
عند فقدان ثابت مؤهل لا يبحث Rails عنه في مجالات الأسماء الأصل. ولكن هناك تحذير: عندما يفتقد ثابت، لا تستطيع Rails معرفة ما إذا كان المشغل مرجعًا نسبيًا أو مرجعًا مؤهلاً.
عند فقدان ثابت مؤهل، لا يبحث ريلز عنه في مجالات الأسماء للأب. ولكن هناك تحذير: عندما يُفقَد ثابت، لا تستطيع ريلز معرفة ما إذا كان الاطلاق (trigger) مرجعًا نسبيًا أو مرجعًا مؤهلًا.


على سبيل المثال، انظر
على سبيل المثال، انظر
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
module Admin
module Admin
  User
end
</syntaxhighlight>


 User
و<syntaxhighlight lang="rails">
 
End
 
و
 
Admin::User
Admin::User
</syntaxhighlight>
</syntaxhighlight>إذا كان <code>User</code> مفقودًا، ففي كلتا الحالتين كل ما يعرفه ريلز أنَّ ثابتًا يسمى "User" مفقودٌ في وحدة تسمى "Admin".
 
إذا كان User  مفقودًا، ففي كلتا الحالتين يعرف Rails أن ثابتًا يسمى "User" مفقود في وحدة تسمى "Admin".


إذا كان هناك User رفيع المستوى روبي سوف يحلها في المثال السابق، لكن لن يكون في الأخير. بشكل عام، لا تحاكي Rails خوارزميات روبي لإحلال الثوابت، ولكنها في هذه الحالة تحاول استخدام الكشف عن الأشياء التالية:
إذا كان هناك <code>User</code> ذو مستوى أعلى، ستستبينه روبي في المثال السابق، لكن لن يكون في الأخير. بشكل عام، لا تحاكي ريلز خوارزميات روبي لاستبيان الثوابت، ولكنها في هذه الحالة تحاول استخدام الكشف عن الأشياء التالية:


" إذا كان أي من مجالات الأسماء الأصل للصنف أو الوحدة النمطية يحتوي على ثابت مفقود، يفترض Rails أن المرجع هو نسبي. خلاف ذلك مؤهل. "
" إذا كان أي من مجالات أسماء الأب للصنف أو الوحدة يحتوي على ثابت مفقود، يفترض ريلز أن المرجع هو نسبي. خلاف ذلك، يفترض أنه مؤهل. "


على سبيل المثال، إذا كان هذا الرمز يشغل التحميل التلقائي:
على سبيل المثال، إذا كانت هذه الشيفرة تُنفِّذ التحميل التلقائي:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
Admin::User
Admin::User
</syntaxhighlight>
</syntaxhighlight>


والثابت User موجود بالفعل في Object، ليس من الممكن أن الوضع:
والثابت <code>User</code> موجود بالفعل في <code>Object</code>، ليس من الممكن أنَّ الحالة هي:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
module Admin
module Admin
 
  User
 User
end
 
End
</syntaxhighlight>
</syntaxhighlight>


لأنه بخلاف ذلك، كان روبي قد حل User ولم يكن قد شُغل أي تحميل ذاتي في المقام الأول. وبالتالي، يفترض Rails مرجع مؤهلاً ويعتبر ملف admin / user.rb والدليل admin/user أن تكون الخيارات صالحة فقط.
لأنه بخلاف ذلك، كان روبي قد استبين <code>User</code> ولم يكن قد أُجرِي أي تحميل تلقائي في المقام الأول. وبالتالي، يفترض ريلز مرجعًا مؤهلًا ويعتبر الملف admin/user.rb والمجلد admin/user خيارين صالحين فقط.


من الناحية العملية، يعمل هذا بشكل جيد طالما أن التداخل يطابق كافة مساحات أسماء الأصل على التوالي وتعرف الثوابت التي تجعل القاعدة تنطبق في ذلك الوقت.
من الناحية العملية، يعمل هذا بشكل جيد طالما أن التداخل يطابق كافة مجالات أسماء الأب على التوالي وتُعرَف الثوابت التي تجعل القاعدة تنطبق في ذلك الوقت.


ومع ذلك، يحدث التحميل التلقائي عند الطلب. إذا لم يُحمل User ذي المستوى الأعلى عن طريق الصدفة، يفترض Rails مرجعًا نسبيًا بموجب العقد.
ومع ذلك، يحدث التحميل التلقائي عند الطلب. إذا لم يُحمَّل <code>User</code> ذي المستوى الأعلى عن طريق الصدفة، يفترض ريلز مرجعًا نسبيًا بموجب الاتفاق.


إن تسمية التضارب من هذا النوع أمر نادر في الممارسة، ولكن إذا حدث ذلك، فإن required_dependency يوفر حلاً من خلال التأكد من أن الثابت المطلوب لتحفيز الكشف عن مجريات الأمور محدد في المكان المتضارب.
إن تسمية التضارب من هذا النوع أمر نادر الحدوث عمليًّا، ولكن إذا حدث ذلك، فإن <code>required_dependency</code> يوفر حلًا من خلال التأكد من أن الثابت المطلوب لبدء تنفيذ الكشف (heuristic) مُعرَّف في المكان التضارب.


=== الوحدات التلقائية ===
=== الوحدات التلقائية ===
عندما تعمل وحدة نمطية مثل مجال اسم، لا يتطلب Rails التطبيق لتعريف ملف له، الدليل المتطابق لمجال الاسم كافٍ.
عندما تعمل وحدة مثل مجال اسم، لا يطلب ريلز التطبيق لتعريف ملف له، إذ المجلد المتطابق لمجال الاسم كافٍ.
 
لنفترض أن التطبيق يحتوي على مكتب خلفي يُخزن وحدات التحكم فيه في app/controllers/admin.


إذا لم تُحمل وحدة Admin حتى الآن عند إصابة Admin::UsersController، فإن Rails يحتاج أولاً إلى تحميل الثابت Admin أولاً.
لنفترض أن التطبيق يحتوي على مكتب خلفي يُخزن وحدات التحكم فيه في app/controllers/admin. إذا لم تُحمل الوحدة <code>Admin</code> حتى الآن عند الوصول إلى تنفيذ <code>Admin::UsersController</code>، فإن ريلز يحتاج أولًا إلى تحميل الثابت <code>Admin</code>.


إذا كان autoload_paths يحتوي على ملف يسمى admin.rb Rails سيُحمل هذا الملف، ولكن إذا لم يكن هناك مثل هذا الملف وعُثر على دليل يسمى admin، يُنشأ Rails وحدة فارغة ويُعينه إلى الثابت Admin أثناء الطيران.
إذا كان <code>autoload_paths</code> يحتوي على ملف يسمى admin.rb، سيُحمل ريلز هذا الملف، ولكن إذا لم يكن هناك مثل هذا الملف وعُثر على مجلد يسمى admin، يُنشأ ريلز وحدة فارغة ويُعينه إلى الثابت <code>Admin</code> مباشرةً.


=== الإجراء العام ===
=== الإجراء العام ===
تم ذكر فقدان المراجع النسبية في cref حيث سُجلت، وتم ذكر فقدان المراجع المؤهلة في الأصل (راجع خوارزمية الاستبيان للثوابت النسبية في بداية هذا الدليل لتعريف cref، و خوارزمية الاستبيان للثوابت المؤهلة لتعريف الأصل).
تم ذكر فقدان المراجع النسبية في <code>cref</code> حيث سُجلت، وتم ذكر فقدان المراجع المؤهلة في العناصر الأب الخاصة بها (راجع قسم خوارزمية الاستبيان للثوابت النسبية في بداية هذا الدليل لتعريف <code>cref</code>، و خوارزمية الاستبيان للثوابت المؤهلة لتعريف الأب [parent]).


يكون الإجراء للتحميل التلقائي للثابت  C في الموقف التعسفي كما يلي:
يكون الإجراء للتحميل التلقائي للثابت <code>C</code> في الموقف التعسفي كما يلي:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
if the class or module in which C is missing is Object
if the class or module in which C is missing is Object
 
  let ns = ''
 let ns = <nowiki>''</nowiki>
 
else
else
 
  let M = the class or module in which C is missing
 let M = the class or module in which C is missing
 
  if M is anonymous
 if M is anonymous
    let ns = ''
 
  else
   let ns = <nowiki>''</nowiki>
    let ns = M.name
 
  end
 else
 
   let ns = M.name
 
 end
 
end
end
 
loop do
loop do
 
  # ابحث عن ملف عادي
 # ابحث عن ملف عادي.
  for dir in autoload_paths
 
    if the file "#{dir}/#{ns.underscore}/c.rb" exists
 for dir in autoload_paths
      load/require "#{dir}/#{ns.underscore}/c.rb"
 
   if the file "#{dir}/#{ns.underscore}/c.rb" exists
      if C is now defined
 
        return
     load/require "#{dir}/#{ns.underscore}/c.rb"
      else
 
        raise LoadError
     if C is now defined
      end
 
    end
       return
  end
 
     else
  # ابحث عن وحدة تلقائية
 
  for dir in autoload_paths
       raise LoadError
    if the directory "#{dir}/#{ns.underscore}/c" exists
 
      if ns is an empty string
     end
        let C = Module.new in Object and return
 
      else
   end
        let C = Module.new in ns.constantize and return
 
      end
 end
    end
 
  end
 # ابحث عن وحدة تلقائية.
 
  if ns is empty
 for dir in autoload_paths
    # وصلنا إلى المستوى الأعلى دون العثور على الثابت
 
    raise NameError
   if the directory "#{dir}/#{ns.underscore}/c" exists
  else
 
    if C exists in any of the parent namespaces
     if ns is an empty string
      # كشف الثوابت المؤهلة
 
      raise NameError
       let C = Module.new in Object and return
    else
 
      # حاول مرة أخرى في مجال الاسم للأب
     else
      let ns = the parent namespace of ns and retry
 
    end
       let C = Module.new in ns.constantize and return
  end
 
end
     end
 
   end
 
 end
 
 if ns is empty
 
   #وصلنا إلى المستوى الأعلى دون العثور على الثابت.
 
   raise NameError
 
 else
 
   if C exists in any of the parent namespaces
 
     # الثوابت المؤهلة الإرشادية.
 
     raise NameError
 
   else
 
     # حاول مرة أخرى في مساحة الاسم الأصل.
 
     let ns = the parent namespace of ns and retry
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


== Require_dependency ==
== <code>require_dependency</code> ==
يُشغل التحميل التلقائي عند الطلب ولذلك قد يكون التعليمات البرمجية التي تستخدم ثابت معين محددة بالفعل أو قد يؤدي إلى التحميل التلقائي. يعتمد ذلك على مسار التنفيذ وقد يختلف بين عمليات التنفيذ.
يُشغَّل التحميل التلقائي عند الطلب، ولذلك قد يكون التعليمات البرمجية التي تستخدم ثابت معين محددة بالفعل أو قد يؤدي إلى التحميل التلقائي. يعتمد ذلك على مسار التنفيذ وقد يختلف بين عمليات التنفيذ.


ومع ذلك، هناك أوقات تريد فيها التأكد من معرفة ثابت معين عندما يصل التنفيذ إلى رمز ما. يوفر require_dependency طريقة لتحميل ملف باستخدام آلية التحميل الحالية، وتتبع الثوابت المحددة في هذا الملف كما لو تُحملت تلقائيًا لإعادة تحميلها حسب الحاجة.
ومع ذلك، هناك أوقات تريد فيها التأكد من معرفة ثابت معين عندما يصل التنفيذ إلى رمز ما. يوفر <code>require_dependency</code> طريقة لتحميل ملف باستخدام آلية التحميل الحالية، وتتبع الثوابت المحددة في هذا الملف كما لو تُحملت تلقائيًا لإعادة تحميلها حسب الحاجة.


نادراً ما نحتاج إلى require_dependency، ولكن نرى بعض حالات الاستخدام في Autoloading و STI وعندما لا يُشغل الثوابت.
نادرًا ما نحتاج إلى <code>require_dependency</code>، ولكن اطلع على حالتي الاستخدام المشار إليها في القسم "[[Rails/autoloading and reloading constants#.D8.A7.D9.84.D8.AA.D8.AD.D9.85.D9.8A.D9.84 .D8.A7.D9.84.D8.AA.D9.84.D9.82.D8.A7.D8.A6.D9.8A .D9.88 STI|التحميل التلقائي و STI]]" التي [[Rails/autoloading and reloading constants#.D8.B9.D9.86.D8.AF.D9.85.D8.A7 .D9.84.D8.A7 .D8.AA.D9.83.D9.88.D9.86 .D8.A7.D9.84.D8.AB.D9.88.D8.A7.D8.A8.D8.AA .D9.85.D9.81.D9.82.D9.88.D8.AF.D8.A9|لا تُشغِّل الثوابت فيها التحميل التلقائي]].


ملاحظة: على عكس autoloading، لا تتوقع require_dependency أن يحدد الملف أي ثابت معين. قد يكون استغلال هذا السلوك ممارسة سيئة بالرغم من ذلك، يجب أن تتطابق الملفات والمسارات الثابتة.
'''تحذير''': على عكس التحميل التلقائي، لا تتوقع <code>require_dependency</code> أن يحدد الملف أي ثابت معين. قد يكون استغلال هذا السلوك ممارسة سيئة بالرغم من ذلك، إذ يجب أن تتطابق الملفات والمسارات الثابتة.


== إعادة التحميل للثوابت ==
== إعادة التحميل للثوابت ==
عندما يكون config.cache_classes false، فإن Rails قادرة على إعادة تحميل الثوابت ذاتية التحميل.
عندما يكون الضبط <code>config.cache_classes</code> معينًا إلى القيمة <code>false</code>، فإن ريلز قادرة على إعادة تحميل الثوابت المحملة تلقائيًّا.
 
على سبيل المثال، إذا كنت في جلسة عمل وحدة التحكم وقمت بتحرير بعض الملفات من وراء الكواليس، فيمكن إعادة تحميل الشيفرة مع reload! أمر:


على سبيل المثال، إذا كنت في جلسة طرفية وقمت بتحرير بعض الملفات من خلف الستار، فيمكن إعادة تحميل الشيفرة مع الأمر <code>reload!‎</code>:<syntaxhighlight lang="shell">
> reload!
> reload!
 
</syntaxhighlight>عند تشغيل التطبيق، يُعاد تحميل الشفرة عندما يتغير شيء ذي صلة بهذه الشيفرة. من أجل القيام بذلك، تراقب ريلز عدد من الأشياء:
عند تشغيل التطبيق، يُعاد تحميل الشفرة عندما يتغير شيء ذي صلة بهذا المنطق. من أجل القيام بذلك، تراقب Rails عدد من الأشياء:
* config/routes.rb.
* config/routes.rb.
* مواقع محلية.
* المحليات (Locales)
* ملفات روبي تحت autoload_paths.
* ملفات روبي ضمن <code>autoload_paths</code>.
* db / schema.rb و db / structure.sql.
* db/schema.rb و db/structure.sql.
إذا تغير أي شيء هناك، فهناك برنامج وسيط يكتشفها ويعيد تحميل الشفرة.
إذا تغير أي شيء هناك، فهناك برنامج وسيط يكتشفها ويعيد تحميل الشيفرة.


Autoloading بتتبع التحميل التلقائي الثوابت. تُنفذ إعادة التحميل عن طريق إزالتها جميعًا من الأصناف والوحدات الخاصة بها باستخدام Module # remove_const. بهذه الطريقة، عندما يُشغل الشيفرة، ستكون هذه الثوابت غير معروفة مرة أخرى، ويُعاد تحميل الملفات عند الطلب.
يتتبع التحميل التلقائي الثوابت المحملة تلقائيًّا. تُنفذ إعادة التحميل عن طريق إزالتها جميعها من الأصناف والوحدات الخاصة بها باستخدام <code>[[Ruby/Module/remove const|Module.remove_const]]</code>. بهذه الطريقة، عندما تُشغل الشيفرة، ستكون هذه الثوابت غير معروفة مرة أخرى، ويُعاد تحميل الملفات عند الطلب.


هذه عملية كل شيء أو لا شيء، Rails لا يحاول إعادة تحميل سوى ما تغيرت لأن التبعيات بين الطبقات يجعل ذلك صعب حقا. بدلا من ذلك، يُمسح كل شيء.
'''تنبيه''': هذه عملية كل شيء أو لا شيء، إذ لا يحاول ريلز إعادة تحميل سوى ما تغير لأن التبعيات بين الطبقات يجعل ذلك أمرٌ محالٌ حقًا. بدلًا من ذلك، يُمسح كل شيء.


== Module#autoload غير متضمنة ==
== التابع <code>[[Ruby/Module/autoload|Module.autoload]]</code> غير مضمن ==
Module#autoload يوفر طريقة كسولة لتحميل الثوابت التي تتكامل تمامًا مع خوارزميات البحث لثوابت روبي، وواجهة برمجة التطبيقات الثابتة الديناميكية، إلخ. إنها شفافة تمامًا.
يوفر التابع <code>[[Ruby/Module/autoload|Module.autoload]]</code> طريقة كسولة لتحميل الثوابت التي تتكامل تمامًا مع خوارزميات البحث لثوابت روبي، وواجهة برمجة التطبيقات الثابتة الديناميكية، ...إلخ. إن شفاف تمامًا.


تستخدم Rails الداخلية استخدام واسع النطاق لتأجيل أكبر قدر ممكن من العمل من عملية التمهيد. ولكن التحميل التلقائي للثوابت لايُنفذ في Rails مع Module#autoload.
تستخدم ريلز داخليًّا ذلك التابع استخدامًا واسع النطاق لتأجيل أكبر قدر ممكن من العمل المراد تنفيذه في عملية الإقلاع. ولكن التحميل التلقائي للثوابت لايُنفذ في ريلز مع <code>[[Ruby/Module/autoload|Module.autoload]]</code>.


أحد التطبيقات الممكنة استناداً إلى Module#autoload سيكون السير إلى شجرة التطبيق وإصدار استدعاءات autoload تُعين أسماء الملفات الموجودة إلى اسمها الثابت التقليدي.
أحد التنفيذات الممكنة التي تستند إلى <code>[[Ruby/Module/autoload|Module.autoload]]</code> هو التدرج في شجرة التطبيق وإصدار استدعاءات <code>autoload</code> التي تُعين أسماء الملفات الموجودة إلى اسمها الثابت التقليدي.


هناك عدد من الأسباب التي تمنع Rails من استخدام هذا التنفيذ.
هناك عدد من الأسباب التي تمنع ريلز من استخدام هذا التنفيذ.


على سبيل المثال، لا يستطيع Module#autoload  إلا تحميل الملفات باستخدام require، لذا لن تكون عملية إعادة التحميل ممكنة. ليس ذلك فحسب، بل يستخدم require الداخلي الذي لا Kernel#require.
على سبيل المثال، لا يستطيع <code>[[Ruby/Module/autoload|Module.autoload]]</code>  إلا تحميل الملفات باستخدام <code>require</code>، لذا لن تكون عملية إعادة التحميل ممكنة. ليس ذلك فحسب، بل يستخدم <code>require</code> الداخلي الذي يختلف عن <code>[[Ruby/Kernel/require|Kernel.require]]</code>.


ثم، فإنه لا يوفر أية طريقة لإزالة التعريفات في حالة حذف ملف. في حالة إزالة الثابت باستخدام Module#remove_const لا يُشغل autoload مرة أخرى. كما أنه لا يدعم الأسماء المؤهلة، لذلك يجب ترجمة الملفات ذات مجالات الأسماء أثناء سير الشجرة لتثبيت استدعاءات autoload الخاصة، لكن هذه الملفات قد تحتوي على مراجع ثابتة لم تُكون بعد.
بعد ذلك، فإنه لا يوفر أية طريقة لإزالة التعريفات في حالة حذف ملف. في حالة إزالة الثابت باستخدام <code>[[Ruby/Module/remove const|Module.remove_const]]</code>، لا يستدعى <code>autoload</code> الخاص به مرة أخرى. كما أنه لا يدعم الأسماء المؤهلة، لذلك يجب ترجمة الملفات ذات مجالات الأسماء أثناء التدرج في الشجرة لتثبيت استدعاءات <code>autoload</code> الخاصة، لكن هذه الملفات قد تحتوي على مراجع ثابتة لم تُضبَط بعد.


سيكون التنفيذ الذي يعتمد على Module#autoload رائعًا لكن، كما ترى، على الأقل اعتبارًا من اليوم غير ممكن. تُنفذ التحميل التلقائي في Rails مع  Module#const_missing وهذا هو السبب في أنه يحتوي على العقد الخاص به، موثقة في هذا الدليل.
سيكون التنفيذ الذي يعتمد على <code>[[Ruby/Module/autoload|Module.autoload]]</code> رائعًا لكن، كما ترى، على الأقل اعتبارًا من اليوم غير ممكن. يُنفَّذ التحميل التلقائي في ريلز مع <code>[[Ruby/Module/const missing|Module.const_missing]]</code> وهذا هو السبب في أنه يحتوي على اتفاقية خاص به، موثقة في هذا الدليل.


== Gotchas المشترك ==
== مشاكل شائعة ==


=== التشعب والثوابت المؤهلة ===
=== التشعب والثوابت المؤهلة ===
دعونا ننظر:
افترض أن لدينا الشيفرة التالية:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
module Admin
module Admin
  class UsersController < ApplicationController
    def index
      @users = User.all
    end
  end
end
</syntaxhighlight>


 class UsersController < ApplicationController
و<syntaxhighlight lang="rails">
 
   def index
 
     @users = User.all
 
   end
 
 end
 
End
 
و
 
class Admin::UsersController < ApplicationController
class Admin::UsersController < ApplicationController
  def index
    @users = User.all
  end
end
</syntaxhighlight>لاستبيان <code>User</code>، يتحقق روبي من <code>Admin</code> في الحالة السابقة، لكنه غير موجود في الأخير لأنه لا ينتمي إلى التشعب (راجع قسم [[Rails/autoloading and reloading constants#.D8.A7.D9.84.D8.AA.D8.B4.D8.B9.D8.A8|التشعب]] و<nowiki/>[[Rails/autoloading and reloading constants#.D8.AE.D9.88.D8.A7.D8.B1.D8.B2.D9.85.D9.8A.D8.A7.D8.AA .D8.A7.D9.84.D8.A7.D8.B3.D8.AA.D8.A8.D9.8A.D8.A7.D9.86 .28Resolution Algorithms.29|خوارزميات الاستبيان]]).


 def index
لسوء الحظ، لا يعرف التحميل التلقائي لريلز التشعب في المكان الذي كان فيه الثابت مفقودًا، وبالتالي لا يكون قادرًا على التصرف كما يفعل روبي. على وجه الخصوص، سيُحمَّل <code>Admin::User</code> تلقائيًّا في كلتا الحالتين.
 
   @users = User.all
 
 end
 
End
</syntaxhighlight>
 
لحل User روبي يتحقق من Admin في الحالة السابقة، لكنه لا يوجد في الأخير لأنه لا ينتمي إلى التداخل (راجع التشعب و خوارزميات الاستبيان).
 
لسوء الحظ، لا تعرف التحميل التلقائي ل Rails التشعب في المكان الذي كان فيه الثابت مفقودًا، وبالتالي لا يكون قادرًا على التصرف كما يفعل روبي. على وجه الخصوص، سيحصل على التحميل التلقائي Admin::User في كلتا الحالتين.


على الرغم من أن الثوابت المؤهلة التي تحتوي على كلمات أساسية class و module قد تعمل تقنيًا مع التحميل التلقائي في بعض الحالات، فمن الأفضل استخدام الثوابت النسبية بدلاً من ذلك:
على الرغم من أن الثوابت المؤهلة التي تحتوي على الكلمتين المفتاحيتين <code>class</code> و <code>module</code> قد تعمل تقنيًا مع التحميل التلقائي في بعض الحالات، فمن الأفضل استخدام الثوابت النسبية بدلًا من ذلك:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
module Admin
module Admin
 
  class UsersController < ApplicationController
 class UsersController < ApplicationController
    def index
 
      @users = User.all
   def index
    end
 
  end
     @users = User.all
end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


=== التحميل التلقائي و STI ===
=== التحميل التلقائي و STI ===
وراثة جدول فردي (STI) هي إحدى ميزات السجل النشط التي تمكّن من تخزين تسلسل هرمي للنماذج في جدول واحد. واجهة برمجة التطبيقات (API) لهذه النماذج تدرك التسلسل الهرمي وتلخص بعض الاحتياجات العامة. على سبيل المثال ، بالنظر إلى هذه الأصناف:
توريث الجدول الواحد (STI) هي إحدى ميزات [[Rails/active record|Active Record]] التي تمكّن من تخزين تسلسل هرمي للنماذج في جدول واحد. واجهة برمجة التطبيقات (API) لهذه النماذج تدرك التسلسل الهرمي وتلخص بعض الاحتياجات العامة. على سبيل المثال، بالنظر إلى هذه الأصناف:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/polygon.rb
# app/models/polygon.rb
 
class Polygon < ApplicationRecord
class Polygon < ApplicationRecord
end
end
 
<nowiki>#</nowiki> app/models/triangle.rb
# app/models/triangle.rb
 
class Triangle < Polygon
class Triangle < Polygon
end
end
 
<nowiki>#</nowiki> app/models/rectangle.rb
# app/models/rectangle.rb
 
class Rectangle < Polygon
class Rectangle < Polygon
 
end
End
</syntaxhighlight>
</syntaxhighlight>


ينشئ Triangle.create صفًا يمثل المثلث، وينشئ Rectangle.create صف يمثل مستطيلاً. إذا كان المعرف هو الرقم التعريفي لسجل موجود، فإن (Polygon.find (id ترجع كائن من النوع الصحيح.
ينشئ <code>Triangle.create</code> صفًا (row) يمثل المثلث، وينشئ <code>Rectangle.create</code> صفًا يمثل مستطيلًا. إذا كان المُعرِّف (id) هو المُعرِّف ID لسجل موجود، فسيعيد <code>(Polygon.find(id</code> كائنًا من النوع الصحيح.


التوابع التي تعمل على مجموعات هي أيضا على بينة من التسلسل الهرمي. على سبيل المثال، ترجع Polygon.all كافة سجلات الجدول، لأن كافة المستطيلات والمثلثات عبارة عن مضلعات. يعتني السجل النشط بإعادة نسخ من فئتها المتطابقة في مجموعة النتائج.
التوابع التي تعمل على مجموعات هي أيضًا على بينة من التسلسل الهرمي. على سبيل المثال، يعيد <code>Polygon.all</code> كافة سجلات الجدول، لأن كافة المستطيلات والمثلثات عبارة عن مضلعات. يعتني [[Rails/active record|Active Record]] بإعادة نسخ من الصنف المقابل لها في مجموعة النتائج.


والتحميل التلقائي أنواع حسب الحاجة. على سبيل المثال، إذا كان Polygon.first مستطيلاً ولم يُحمل Rectangle بعد، السجل النشط يُحمله تلقائيًا وينشئ السجل بشكل صحيح.
تُحمَّل الأنواع تلقائيًّا حسب الحاجة. على سبيل المثال، إذا كان <code>Polygon.first</code> مستطيلًا ولم يُحمَّل <code>Rectangle</code> بعد، يحمله [[Rails/active record|Active Record]] تلقائيًا وينشئ السجل بشكل صحيح.


كل شيء جيد، ولكن إذا أردنا العمل على بعض الأصناف الفرعية، بدلاً من تنفيذ استعلامات تستند إلى صنف الجذر، فستصبح الأشياء مثيرة للاهتمام.
كل شيء جيد، ولكن إذا أردنا العمل على بعض الأصناف الفرعية، بدلًا من تنفيذ استعلامات تستند إلى صنف الجذر، فستصبح الأشياء مثيرة للاهتمام.


أثناء العمل مع Polygon، لا تحتاج إلى أن تكون على دراية بجميع أحفادها، لأن أي شيء في الجدول يكون مضلعًا بحكم التعريف، ولكن عند العمل مع الأصناف الفرعية يجب أن يكون السجل النشط قادراً على تعداد الأنواع التي يبحث عنها. دعونا نرى مثال.
أثناء العمل مع <code>Polygon</code>، لا تحتاج إلى أن تكون على دراية بجميع أحفاده، لأن أي شيء في الجدول يكون مضلعًا بحكم التعريف، ولكن عند العمل مع الأصناف الفرعية يجب أن يكون [[Rails/active record|Active Record]] قادرًا على تعداد الأنواع التي يبحث عنها. دعونا نرى مثالًا عن ذلك.


يُحمل Rectangle.all المستطيلات فقط عن طريق إضافة قيد نوع إلى الاستعلام:
يُحمل <code>Rectangle.all</code> المستطيلات فقط عن طريق إضافة قيد نوع إلى الاستعلام:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="sql">
SELECT "polygons".* FROM "polygons"
SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")
WHERE "polygons"."type" IN ("Rectangle")
</syntaxhighlight>
</syntaxhighlight>


دعونا نقدم الآن صنف فرعية من Rectangle:
دعونا نقدم الآن صنفًا فرعيًّا من <code>Rectangle</code>:<syntaxhighlight lang="rails">
 
# app/models/square.rb
<nowiki>#</nowiki> app/models/square.rb
 
class Square < Rectangle
class Square < Rectangle
 
end
End
</syntaxhighlight>يجب أن يعيد <code>Rectangle.all</code> الآن المستطيلات (rectangles) والمربعات (squares):<syntaxhighlight lang="sql">
 
Rectangle.all يجب الآن إرجاع المستطيلات والمربعات:
 
SELECT "polygons".* FROM "polygons"
SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle", "Square")
WHERE "polygons"."type" IN ("Rectangle", "Square")
</syntaxhighlight>ولكن هناك تحذير هنا: كيف يعرف [[Rails/active record|Active Record]] أن الصنف <code>Square</code> موجودة حتمًا؟


ولكن هناك تحذير هنا: كيف يعرف السجل النشط أن صنف Square موجودة على الإطلاق؟
حتى إذا كان الملف app/models/square.rb موجودًا ويعرِّف الصنف <code>Square</code>، إذا لم تستخدم أي شيفرة بعد ذلك الصنف، فإن <code>Rectangle.all</code> يصدر الاستعلام.<syntaxhighlight lang="sql">
 
حتى إذا كان ملف app/models/square.rb موجودًا ويحدد صنف Square، إذا لم يستخدم أي رمز بعد ذلك، فإن Rectangle.all يصدر الاستعلام.
 
SELECT "polygons".* FROM "polygons"
SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")
WHERE "polygons"."type" IN ("Rectangle")
</syntaxhighlight>هذا ليس خطأ، ويشمل الاستعلام جميع الأحفاد المعروفة من <code>Rectangle</code>.


هذا ليس خطأ، ويشمل الاستعلام جميع الأحفاد المعروفة من Rectangle.
طريقة للتأكد من أن هذا يعمل بشكل صحيح بغض النظر عن ترتيب التنفيذ هي تحميل الأصناف الفرعية المباشرة يدويًّا في أسفل الملف الذي يعرّف كل صنف وسيط (intermediate class):
 
طريقة للتأكد من أن هذا يعمل بشكل صحيح بغض النظر عن ترتيب التنفيذ هو يدوياً تحميل الأصناف الفرعية المباشرة في أسفل الملف الذي يعرّف كل صنف متوسطة:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/rectangle.rb
# app/models/rectangle.rb
 
class Rectangle < Polygon
class Rectangle < Polygon
end
end
require_dependency 'square'
require_dependency 'square'
</syntaxhighlight>
</syntaxhighlight>


يجب أن يحدث هذا لكل صنف وسيطة (غير جذر وغير ورقة). لا يقوم نطاق الجذر بتوسيع نطاق البحث حسب النوع، وبالتالي لا يلزم بالضرورة معرفة كل أحفاده.
يجب أن يحدث هذا لكل صنف وسيط (ليس جذرًا [root] وليس ورقةً [leaf]). لا يوسع نطاق الجذر نطاق البحث حسب النوع، وبالتالي لا يلزم بالضرورة معرفة كل أحفاده.


=== التحميل التلقائي والطلب ===
=== التحميل التلقائي والطلب عبر require ===
لا يجب مطلقًا طلب الملفات التي تحدد الثوابت التي ستُحمل تلقائيًا:
لا يجب مطلقًا طلب الملفات التي تعرِّف الثوابت المراد تحميلها تلقائيًا:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
require 'user' # DO NOT DO THIS
require 'user' # لا تفعل هذا
 
class UsersController < ApplicationController
class UsersController < ApplicationController
 
  ...
 ...
end
 
End
</syntaxhighlight>
</syntaxhighlight>


هناك نوعان من gotchas محتملة هنا في وضع التطوير:
هناك نوعان من المشكلات المحتملة هنا في وضع التطوير:


1- إذا تحمل User تلقائيًا قبل الوصول إلى require، فسيشغل app/models/user.rb مرة أخرى نظرًا لأن التحميل لا يُحدّث $ LOADED_FEATURES.
1- إذا جرى تحميل <code>User</code> تلقائيًا قبل الوصول إلى <code>require</code>، فسيُشغَّل app/models/user.rb مرة أخرى نظرًا لأن التحميل لا يُحدّث <code>‎$LOADED_FEATURES</code>.


2- إذا كانت require  قيد التشغيل أولًا، فإن Rails لا يضع علامة على User كتحميل تلقائي لثابت ولا يُعاد تحميل التغييرات على app/models/user.rb.
2- إذا نُفِّذ <code>require</code> أولًا، فإن ريلز لا يضع علامة على <code>User</code> على أنه ثابت محمَّل تلقائيًّا ولا يُعاد تحميل التغييرات في app/models/user.rb.


فقط اتبع التدفق واستخدم التحميل التلقائي للثابت دائما، أبدا لا تخلط بين التحميل التلقائي و require. كحل أخير، إذا كان بعض الملفات يحتاج إلى تحميل ملف معين استخدم require_dependency للعب لطيف مع التحميل التلقائي للثابت. نادرًا ما يكون هذا الخيار مطلوبًا في الممارسة.
فقط اتبع التدفق واستخدم التحميل التلقائي للثابت دائما، لا تخلط أبدًا بين التحميل التلقائي و <code>require</code>. كحل أخير، إذا كان بعض الملفات التي تحتاج إلى تحميل ملف معين، فاستخدم <code>require_dependency</code> المناسبة جدًا لتحميل ثابت تلقائيًّا. نادرًا ما يكون هذا الخيار مطلوبًا عمليًّا.


وبالطبع، فإن استخدام ما يتطلب من ملفات حُملت تلقائيًا لتحميل مكتبات عادية تابعة لجهات خارجية أمر جيد، و Rails قادرة على تمييز ثوابتها، ولا يُوضع علامة عليها بأنها تحميل تلقائي.
وبالطبع، فإن استخدام ما يتطلب من ملفات حُمِّلَت تلقائيًا لتحميل مكتبات عادية تابعة لجهات خارجية أمرٌ جيد، وريلز قادرة على تمييز ثوابتها، ولا يُوضع علامة عليها بأنها جرى تحميلها تلقائيًّا.


=== التحميل التلقائي و المهيأت   ===
=== التحميل التلقائي و المهيأت   ===
خذ بعين الاعتبار هذه المهمة في config / initializers / set_auth_service.rb:
خذ بعين الاعتبار هذه المهمة في config/initializers/set_auth_service.rb:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
AUTH_SERVICE = if Rails.env.production?
AUTH_SERVICE = if Rails.env.production?
 
  RealAuthService
 RealAuthService
 
else
else
 
  MockedAuthService
 MockedAuthService
end
 
End
</syntaxhighlight>
</syntaxhighlight>


قد يكون الغرض من هذا الإعداد أن يستخدم التطبيق الصنف التي تتوافق مع البيئة عبر AUTH_SERVICE. في وضع التطوير يحصل على التحميل التلقائي MockedAuthService عند تشغيل المُهيئ. لنفترض أننا نقوم ببعض الطلبات، ونغير تنفيذها، ونضغط على التطبيق مرة أخرى. لدهشتنا لا تنعكس التغييرات. لماذا ا؟
قد يكون الغرض من هذا الإعداد أن يستخدم التطبيق الصنف الذي يتوافق مع البيئة عبر <code>AUTH_SERVICE</code>. في وضع التطوير، يحمَّل <code>MockedAuthService</code> تلقائيًّا عند تشغيل المُهيئ. لنفترض أننا نقوم ببعض الطلبات، ونغير تنفيذها، ونعود للتطبيق مرة أخرى لنندهش بعدم انعكاس التغييرات في تطبيقنا. لماذا؟


كما رأينا سابقاً، يزيل Rails التحميل التلقائي للثوابت، ولكن يخزن AUTH_SERVICE كائن الصنف الأصلي. تالفة، غير قابلة للوصول باستخدام الثابت الأصلي  ولكن وظيفية تماما.
[[Rails/autoloading and reloading constants#.D8.A5.D8.B9.D8.A7.D8.AF.D8.A9 .D8.A7.D9.84.D8.AA.D8.AD.D9.85.D9.8A.D9.84 .D9.84.D9.84.D8.AB.D9.88.D8.A7.D8.A8.D8.AA|كما رأينا سابقًا]]، يزيل ريلز التحميل التلقائي للثوابت، ولكن يخزن <code>AUTH_SERVICE</code> كائن الصنف الأصلي. استخدام الثابت الأصلي هو سلوك عفا عليه الزمن وغير قابل للوصول ولكنه فعال وعملي بشكل ممتاز.


يلخص الشيفرة التالي الحالة:
تلخص الشيفرة التالية الحالة:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
class C
class C
 
  def quack
 def quack
    'quack!'
 
  end
   'quack!'
 
 end
 
end
end
 
X = C
X = C
Object.instance_eval { remove_const(:C) }
Object.instance_eval { remove_const(:C) }
X.new.quack # => quack!
X.new.quack # => quack!
 
X.name     # => C
X.name      # => C
C           # => uninitialized constant C (NameError)
 
C           # => uninitialized constant C (NameError)
</syntaxhighlight>
</syntaxhighlight>


وبسبب ذلك، ليست فكرة جيدة التحميل التلقائي للثوابت عند تهيئة التطبيق.
وبسبب ذلك، ليست فكرة التحميل التلقائي للثوابت جيدة عند تهيئة التطبيق.


في الحالة أعلاه، يمكننا تنفيذ نقطة وصول ديناميكية:
في الحالة أعلاه، يمكننا تنفيذ نقطة وصول ديناميكية:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/auth_service.rb
# app/models/auth_service.rb
 
class AuthService
class AuthService
 
  if Rails.env.production?
 if Rails.env.production?
    def self.instance
 
      RealAuthService
   def self.instance
    end
 
  else
     RealAuthService
    def self.instance
 
      MockedAuthService
   end
    end
 
  end
 else
end
 
   def self.instance
 
     MockedAuthService
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


واستخدم التطبيق AuthService.instance بدلاً من ذلك. ستُحمل AuthService عند الطلب وتكون autoload-friendly.
ويتوجب على التطبيق استخدام <code>AuthService.instance</code> بدلًا من ذلك. ستُحمل <code>AuthService</code> عند الطلب.


=== required_dependency و المُهيآت ===
=== التابع <code>required_dependency</code> والمهيآت ===
كما رأينا من قبل، need_dependency تحميل الملفات بطريقة autoloading-friendly. ومع ذلك، عادةً ما لا يكون مثل هذا الاستدعاء منطقيًا في المُهيئ.
كما رأينا من قبل، يحمِّل <code>need_dependency</code> الملفات بطريقة جيدة. ومع ذلك، عادةً ما لا يكون مثل هذا الاستدعاء منطقيًا في المُهيئ.


يمكن للمرء أن يفكر في إجراء بعض استدعاءات relay_dependency في المُهيئ للتأكد من تحميل ثوابت معينة مقدما، على سبيل المثال كمحاولة لمعالجة gotcha مع STIs.
يمكن للمرء أن يفكر في إجراء بعض استدعاءات <code>[[Rails/autoloading and reloading constants#require dependency|require_dependency]]</code> في المُهيئ للتأكد من تحميل ثوابت معينة مقدمًا، على سبيل المثال كمحاولة لمعالجة [[Rails/autoloading and reloading constants#.D8.A7.D9.84.D8.AA.D8.AD.D9.85.D9.8A.D9.84 .D8.A7.D9.84.D8.AA.D9.84.D9.82.D8.A7.D8.A6.D9.8A .D9.88 STI|مشكلات مع توريثات الجدول الواحد (STIs)]].


المشكلة هي، في وضع التطوير يمسح التحميل التلقائي للثوابت إذا كان هناك أي تغيير ذات الصلة في نظام الملفات. إذا حدث ذلك، فنحن في نفس الموقف الذي أراد المبتدئ تجنبه!
المشكلة هي، في وضع التطوير [[Rails/autoloading and reloading constants#.D8.A5.D8.B9.D8.A7.D8.AF.D8.A9 .D8.A7.D9.84.D8.AA.D8.AD.D9.85.D9.8A.D9.84 .D9.84.D9.84.D8.AB.D9.88.D8.A7.D8.A8.D8.AA|تُمسَح الثوابت المحملة تلقائيًّا]] إذا كان هناك أي تغيير ذي صلة في نظام الملفات. إذا حدث ذلك، فنحن في نفس الموقف الذي أراد المهيئ تجنبه!


الإستدعاء إلى require_dependency يجب أن تكون مكتوبة بشكل استراتيجي في مواقع التحميل التلقائي.
الإستدعاء إلى [[Rails/autoloading and reloading constants#require dependency|<code>require_dependency</code>]] يجب أن يكون مكتوب بشكل استراتيجي في مواقع التحميل التلقائي.


=== عندما لا تكون الثوابت مفقودة ===
=== عندما لا تكون الثوابت مفقودة ===


==== المراجع النسبية ====
==== المراجع النسبية ====
دعونا ننظر في جهاز محاكاة الطيران. التطبيق يحتوي على نموذج رحلة افتراضي:
دعونا ننظر في جهاز محاكاة الطيران. يحتوي التطبيق على نموذج رحلة (flight model) افتراضي:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/flight_model.rb
# app/models/flight_model.rb
 
class FlightModel
class FlightModel
 
end
End
</syntaxhighlight>
</syntaxhighlight>


يمكن تجاوزها من قبل كل طائرة، على سبيل المثال:
يمكن استبدالها من قبل كل طائرة، على سبيل المثال:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/bell_x1/flight_model.rb
# app/models/bell_x1/flight_model.rb
 
module BellX1
module BellX1
 
  class FlightModel < FlightModel
 class FlightModel < FlightModel
  end
 
 end
 
end
end
 
<nowiki>#</nowiki> app/models/bell_x1/aircraft.rb
# app/models/bell_x1/aircraft.rb
 
module BellX1
module BellX1
 
  class Aircraft
 class Aircraft
    def initialize
 
      @flight_model = FlightModel.new
   def initialize
    end
 
  end
     @flight_model = FlightModel.new
end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


يريد المُهيئ إنشاء BellX1 :: FlightModel وتداخل يحتوي على BellX1، التي تبدو جيدة. ولكن إذا تحمل نموذج الطيران الافتراضي ولم يُثبت نموذج Bell-X1، فسيكون بمقدور المترجم حل ملف FlightModel ذي المستوى الأعلى وبالتالي لا يُشغل ميزة التحميل التلقائي لـ BellX1 :: FlightModel.
يريد المُهيئ إنشاء <code>BellX1::FlightModel</code> وتداخل يحتوي على <code>BellX1</code>، الذي يبدو جيدًا إلى الآن. ولكن إذا حُمِّل نموذج الطيران الافتراضي (default flight model) ولم يُحمَّل النموذج <code>Bell-X1</code>، فسيكون بمقدور المترجم استبيان <code>FlightModel</code> ذي المستوى الأعلى وبالتالي لا يُشغل ميزة التحميل التلقائي لـ <code>BellX1::FlightModel</code>.


يعتمد هذا الرمز على مسار التنفيذ.
تعتمد تلك الشيفرة على مسار التنفيذ.


يمكن في الغالب حل هذا النوع من الغموض باستخدام ثوابت مؤهلة:
يمكن في الغالب استبيان هذا النوع من الغموض باستخدام ثوابت مؤهلة:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
module BellX1
module BellX1
 
  class Plane
 class Plane
    def flight_model
 
      @flight_model ||= BellX1::FlightModel.new
   def flight_model
    end
 
  end
     @flight_model ||= BellX1::FlightModel.new
end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


أيضا، require_dependency هو الحل:
أيضا، <code>require_dependency</code> هو الحل:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
require_dependency 'bell_x1/flight_model'
require_dependency 'bell_x1/flight_model'
 
module BellX1
module BellX1
 
  class Plane
 class Plane
    def flight_model
 
      @flight_model ||= FlightModel.new
   def flight_model
    end
 
  end
     @flight_model ||= FlightModel.new
end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


==== مراجع مؤهلة ====
==== مراجع مؤهلة ====
ملاحظة: هذه gotcha ممكن فقط في روبي <2.5.
'''تحذير''': هذه المشكلة يُحتمَل حدوثها في الإصدارات التي قبل 2.5 من [[Ruby|روبي]].


معطى
خذ مثلًا الشيفرة التالية:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/hotel.rb
# app/models/hotel.rb
 
class Hotel
class Hotel
end
end
 
<nowiki>#</nowiki> app/models/image.rb
# app/models/image.rb
 
class Image
class Image
end
end
 
<nowiki>#</nowiki> app/models/hotel/image.rb
# app/models/hotel/image.rb
 
class Hotel
class Hotel
 
  class Image < Image
 class Image < Image
  end
 
end
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


التعبير Hotel :: Image غامض لأنه يعتمد على مسار التنفيذ.
التعبير <code>Hotel::Image</code> غامض لأنه يعتمد على مسار التنفيذ.


كما رأينا من قبل، يبحث روبي عن الثابت في Hotel وأسلافه. إذا تحمل app/models/image.rb ولكن app/models/hotel/image.rb لا، لم يعثر روبي على Image في Hotel، لكنه في Object:
كما رأينا من قبل، يبحث روبي عن الثابت في <code>Hotel</code> وأسلافه. إذا حُمِّل app/models/image.rb ولكن لم يُحمَّل app/models/hotel/image.rb، لن يعثر روبي على <code>Image</code> في <code>Hotel</code>، لكنه سيعثر عليها في <code>Object</code>:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null
$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null
 
Image # Hotel::Image وليس
Image # NOT Hotel::Image!
</syntaxhighlight>
</syntaxhighlight>


يجب أن يكون رمز تقييم Hotel :: Image للتأكد من تحميل app/models/hotel/image.rb، ربما مع require_dependency.
يجب أن يكون تقييم الشيفرة <code>Hotel::Image</code> متأكدًا من تحميل app/models/hotel/image.rb، ربما مع <code>require_dependency</code>.


في هذه الحالات، يصدر المترجم تحذيرًا على الرغم من:
في هذه الحالات، يصدر المترجم تحذيرًا على الرغم من ذلك:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
warning: toplevel constant Image referenced by Hotel::Image
warning: toplevel constant Image referenced by Hotel::Image
</syntaxhighlight>
</syntaxhighlight>


يمكن ملاحظة هذا الاستبيان الثابت المذهل مع أي صنف مؤهلة:
يمكن ملاحظة هذا الاستبيان الثابت المذهل مع أي صنف مؤهل:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
2.1.5 :001 > String::Array
2.1.5 :001 > String::Array
(irb):1: warning: toplevel constant Array referenced by String::Array
(irb):1: warning: toplevel constant Array referenced by String::Array
 
=> Array
=> Array
</syntaxhighlight>
</syntaxhighlight>


ملاحظة: للعثور على هذا gotcha  يجب أن تكون مجال الاسم المؤهلة صنف، الكائن ليس سلف من الوحدات.
'''تحذير''': للعثور على هذه المشكلة،  يجب أن يكون مجال الاسم المؤهل صنفًا، إذ <code>[[Ruby/Object|Object]]</code> ليس سلف للوحدات.


=== التحميل التلقائي داخل أصناف Singleton ===
=== التحميل التلقائي داخل الأصناف المفردة ===
لنفترض أن لدينا تعريفات الصنف هذه:
لنفترض أن لدينا تعريفات الصنف التالية:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
<nowiki>#</nowiki> app/models/hotel/services.rb
# app/models/hotel/services.rb
 
module Hotel
module Hotel
 
  class Services
 class Services
  end
 
 end
 
end
end
 
<nowiki>#</nowiki> app/models/hotel/geo_location.rb
# app/models/hotel/geo_location.rb
 
module Hotel
module Hotel
  class GeoLocation
    class << self
      Services
    end
  end
end


 class GeoLocation
   class << self
     Services
   end
 end
End
</syntaxhighlight>
</syntaxhighlight>


إذا كان Hotel::Services معروفة في وقت تحميل app/models/hotel/geo_location.rb، تُحل الـ Services بواسطة Ruby لأن الفندق ينتمي إلى التشعب عندما تُفتح صنف singleton من Hotel :: GeoLocation.
إذا كان <code>Hotel::Services</code> معروف في وقت تحميل app/models/hotel/geo_location.rb، يُستبيَن <code>Services</code> بواسطة روبي لأن <code>Hotel</code> ينتمي إلى التشعب عندما يُفتَح صنف منفرد (singleton class) من <code>Hotel::GeoLocation</code>.


ولكن إذا كان Hotel::Services غير معروفة، تكون Rails غير قادرة على التحميل التلقائي، ويرفع التطبيق NameError.
ولكن إذا كان <code>Hotel::Services</code> غير معروف، تكون ريلز غير قادرة على التحميل التلقائي، ويرمي التطبيق الاستثناء <code>[[Ruby/NameError|NameError]]</code>.


السبب في ذلك هو أن تشغيل التحميل التلقائي لصنف singleton، هو مجهول، وكما رأينا من قبل، Rails فقط تتحقق من مجال الاسم الأعلى في حالة الحافة.
السبب في ذلك هو أن التحميل التلقائي يُشغَّل لصنف منفرد، الذي هو مجهول، وكما [[Rails/autoloading and reloading constants#.D8.A7.D9.84.D8.A5.D8.AC.D8.B1.D8.A7.D8.A1 .D8.A7.D9.84.D8.B9.D8.A7.D9.85|رأينا من قبل]]، تتحقق ريلز فقط من مجال الاسم الأعلى في تلك الحالة الحدية.


الحل السهل لهذا التحذير هو تأهيل الثابت:
الحل السهل لهذا التحذير هو تأهيل الثابت:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
module Hotel
module Hotel
 
  class GeoLocation
 class GeoLocation
    class << self
 
      Hotel::Services
   class << self
    end
 
  end
     Hotel::Services
end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


=== التحميل التلقائي في BasicObject ===
=== التحميل التلقائي في <code>[[Ruby/BasicObject|BasicObject]]</code> ===
لا تملك أحفاد Direct من BasicObject كائن بين أسلافهم ولا يمكن حل ثوابت المستوى الأعلى:
الأحفاد المباشرة من <code>[[Ruby/BasicObject|BasicObject]]</code> لا تملك <code>[[Ruby/Object|Object]]</code> بين أسلافها ولا يمكن استبيان الثوابت ذات المستوى الأعلى:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
class C < BasicObject
class C < BasicObject
 
  String # NameError: uninitialized constant C::String
 String # NameError: uninitialized constant C::String
 
end
end
</syntaxhighlight>
</syntaxhighlight>


عندما يشارك التحميل التلقائي تلك المؤامرة لديها تطور. دعونا ننظر:
عندما يشترك التحميل التلقائي، تنحرف تلك المشكلة وتأخذ منحًا آخر. لنفترض ما يلي:<syntaxhighlight lang="rails">
 
class C < BasicObject
class C < BasicObject
 
  def user
 def user
    User # خطأ
 
  end
   User # WRONG
end
 
</syntaxhighlight>نظرًا لأن ريلز يتحقق من مجال الاسم ذي المستوى الأعلى، يُحمَّل <code>User</code> تلقائيًّا بشكل جيد في المرة الأولى التي يستدعى فيها التابع <code>User</code>. تحصل على الاستثناء فقط إذا كان الثابت <code>User</code> معروفًا عند هذه النقطة، خاصة في الاستدعاء الثاني لـ <code>user</code>:
 end
 
End
 
نظرًا لأن Rails يتحقق من مجال الاسم ذات المستوى الأعلى، يحصل User على التحميل التلقائي بشكل جيد في المرة الأولى التي يستدعى فيها التابع User. تحصل على الاستثناء فقط إذا كان الثابت User معروفًا عند هذه النقطة، خاصة في ثاني استدعاء للـ Use:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
c = C.new
c = C.new
c.user # surprisingly fine, User
c.user # surprisingly fine, User
c.user # NameError: uninitialized constant C::User


c.user # NameError: uninitialized constant C::User
</syntaxhighlight>
</syntaxhighlight>


لأنه يكتشف أن مجال الاسم الأصل تحتوي على الثابت (انظر مراجع مؤهلة).
لأنه يكتشف أن مجال الاسم للأب يحتوي على الثابت (انظر قسم [[Rails/autoloading and reloading constants#.D9.85.D8.B1.D8.A7.D8.AC.D8.B9 .D9.85.D8.A4.D9.87.D9.84.D8.A9|مراجع مؤهلة]]).


كما هو الحال مع روبي الصافي، داخل جسم السلف المباشر من BasicObject، استخدم دائمًا المسارات الثابتة المطلقة:
كما هو الحال مع روبي الصافي، داخل جسم السلف المباشر من <code>BasicObject</code>، استخدم دائمًا المسارات الثابتة المطلقة:
<syntaxhighlight lang="rails">
<syntaxhighlight lang="rails">
class C < BasicObject
class C < BasicObject
 
  ::String # RIGHT
 ::String # RIGHT
 
  def user
 def user
    ::User # RIGHT
 
  end
   ::User # RIGHT
end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


=== التحميل التلقائي في بيئة الاختبار ===
=== التحميل التلقائي في بيئة الاختبار ===
عند تهيئة بيئة الاختبار للتحميل التلقائي، يمكنك التفكير في عدة عوامل.
عند تهيئة بيئة الاختبار (test environment) للتحميل التلقائي، يمكنك التفكير في عدة عوامل.
 
على سبيل المثال، قد يكون من المفيد تشغيل اختباراتك باستخدام إعداد مماثل للإنتاج (<code>config.eager_load = true</code>، و <code>config.cache_classes = true</code>) من أجل اكتشاف أي مشاكل قبل أن تنشر على بيئة الإنتاج (وهذا تعويض عن عدم وجود تعادل dev-prod ). ومع ذلك، فإن هذا سيبطئ وقت الإقلاع للاختبارات الفردية على جهاز التطوير (dev machine، وهو غير متوافق على الفور مع spring، فانظر أدناه). لذا فإن أحد الاحتمالات هو القيام بذلك على جهاز [[wikipedia:Continuous_integration|CI]] فقط (والذي يجب أن يعمل دون spring).


على سبيل المثال، قد يكون من المفيد تشغيل اختباراتك باستخدام إعداد مماثل للإنتاج (config.eager_load = true, config.cache_classes = true) من أجل اكتشاف أي مشاكل قبل أن تصل إلى الإنتاج (وهذا تعويض عن عدم وجود تعادل dev-prod ). ومع ذلك فإن هذا سيبطئ وقت الإقلاع للاختبارات الفردية على جهاز dev (وهو غير متوافق على الفور مع الإنطلاقة انظر أدناه). لذا فإن أحد الاحتمالات هو القيام بذلك على جهاز CI فقط (والذي يجب أن يعمل دون الإنطلاقة).
يمكنك بعد ذلك، على جهاز التطوير، تشغيل الاختبارات الخاصة بك مع أي شيء أسرع (بشكل مثالي <code>config.eager_load = false</code>).


على جهاز التطوير يمكنك بعد ذلك تشغيل الاختبارات الخاصة بك مع أي شيء أسرع (بشكل مثالي config.eager_load = false).
باستخدام أداة التحميل المسبق من [https://github.com/rails/spring Spring] (المضمنة في تطبيقات ريلز الجديدة)، ستحتفظ بشكل مثالي عبر ضبط <code>config.eager_load = false</code> وفقًا للتطوير. في بعض الأحيان قد ينتهي بك الأمر مع ضبط مختلط:<syntaxhighlight lang="rails">
config.eager_load = true, config.cache_classes = true AND config.enable_dependency_loading = true
</syntaxhighlight>راجع [https://github.com/rails/spring/issues/519#issuecomment-348324369 مشكلة Spring]. ومع ذلك، قد يكون من الأسهل الحفاظ على نفس الضبط مثل التطوير، اكتشف حلًا لكل سبب يؤدي إلى فشل التحميل التلقائي (ربما عن طريق نتائج اختبار CI الخاص بك).


باستخدام أداة التحميل المسبق من Spring (المضمنة في تطبيقات rails الجديدة)، ستحتفظ بشكل مثالي بـ config.eager_load = false وفقًا للتطوير. في بعض الأحيان قد ينتهي بك الأمر مع تكوين مختلط (config.eager_load = true ، config.cache_classes = true AND config.enable_dependency_loading = true)، راجع قضية Spring. ومع ذلك، قد يكون من الأسهل الحفاظ على نفس التكوين مثل التطوير، اكتشف - حل كل سبب يؤدي الى فشل التحميل التلقائي  (ربما عن طريق نتائج اختبار CI الخاص بك).
قد تحتاج من حين لآخر إلى التحميل الحثيث عبر <code>eager_load</code> صراحةً باستخدام <code>Rails.application.eager_load!‎</code> في إعداد اختباراتك، إذ قد يحدث هذا إذا كانت [https://stackoverflow.com/questions/25796409/in-rails-how-can-i-eager-load-all-code-before-a-specific-rspec-test اختباراتك تتضمن عدة خيوط].


من حين لآخر قد تحتاج إلى eager_load صراحة باستخدام Rails.application.eager_load! في إعداد اختباراتك - قد يحدث هذا إذا كانت اختباراتك تنطوي على تعدد مؤشرات.
== مصادر ==
* [https://guides.rubyonrails.org/autoloading_and_reloading_constants.html صفحة Autoloading and Reloading Constants في توثيق Ruby On Rails الرسمي.]
[[تصنيف:Rails]]
[[تصنيف:Rails]]
[[تصنيف:Rails Digging Deeper]]
[[تصنيف:Rails Digging Deeper]]

المراجعة الحالية بتاريخ 08:16، 25 مارس 2019

يوثق هذا الدليل طريقة عمل التحميل التلقائي وإعادة تحميل الثوابت. بعد قراءة هذا الدليل، ستتعلم:

  • الجوانب الرئيسية لثوابت لغة روبي.
  • ماهية autoload_paths وكيفية عمل التحميل الحثيث (eager loading) في الإنتاج.
  • كيفية عمل التحميل التلقائي للثابت.
  • ماهية need_dependency.
  • كيفية عمل إعادة التحميل للثابت.
  • حلول للتحميل التلقائي المشترك.

المقدمة

تسمح لغة ريلز بكتابة تطبيقات كما لو حملت الشيفرة الخاص بها مسبقًا.

في أصناف برنامج روبي عادي، تحتاج إلى تحميل جميع اعتماديته (dependencies):

require 'application_controller'
require 'post'
 
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

سرعان ما ترى غريزتنا الروبية بعض التكرار هنا: إذا عُرِّفَت الأصناف في ملفات مطابقة لاسمها، ألا يمكن أن يتم التحميل التلقائي بطريقة ما؟ يمكن حفظ مسح الملف (scanning the file) من أجل الاعتماديات، وهو أمر غير مُجدٍ.

علاوة على ذلك، يحمل Kernel.require الملفات مرة واحدة، ولكن التطوير يكون أكثر سلاسة إذا تحدثت الشيفرة عند تغييرها دون إعادة تشغيل الخادم. سيكون من الجيد أن تكون قادرًا على استخدام Kernel.load في التطوير، و Kernel.require في الإنتاج.

في الواقع، تتوفر هذه الميزات بواسطة ريلز، إذ يكفي أن نكتب فقط:

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

يوثق هذا الدليل كيفية عمل ذلك.

إنه خارج نطاق هذا الدليل توثيق ثوابت لغة روبي، لكننا مع ذلك سنسلط الضوء على بعض المواضيع الرئيسية. حقا استيعاب المقاطع التالية له دور فعال لفهم التحميل التلقائي وإعادة التحميل للثوابت.

منشط الثوابت

بينما الثوابت ليست ذات أهمية في معظم لغات البرمجة، إلا أنها موضوع ثري في لغة روبي.

هذا الأمر يقع خارج نطاق هذا الدليل لتوثيق ثوابت لغة روبي، لكننا مع ذلك سنسلط الضوء على بعض المواضيع الرئيسية. فهم الأقسام التالية بشكل جيد هو أداة مفيدة لفهم التحميل التلقائي وإعادة التحميل للثوابت.

التشعب

يمكن تشعيب تعريفات صنف ووحدة لإنشاء مجالات أسماء:

module XML
  class SAXParser
    # (1)
  end
end

إن التشعب (nesting) في أي مكان معطى هو مجموعة الأصناف المتداخلة المضمنة (enclosing nested class) وكائنات الوحدة الخارجية. يمكن فحص التشعب في أي مكان مع Module.nesting. على سبيل المثال، في المثال السابق، يكون التداخل في (1) هو:

[XML::SAXParser, XML]

من المهم أن نفهم أن التشعب يتكون من كائنات الصنف والوحدة، ولا علاقة له بالثوابت المستخدمة في الوصول إليهم، كما أنه لا علاقة له بأسمائها.

على سبيل المثال، في حين أن هذا التعريف هو مماثل لهذا التعريف السابق:

class XML::SAXParser
  # (2)
end

التشعب في (2) مختلف:

[XML::SAXParser]

لا تنتمي XML إليها.

يمكننا أن نرى في هذا المثال أن اسم الصنف أو الوحدة التي تنتمي إلى تشعب معين لا يرتبط بالضرورة مع مجالات الأسماء الموجودة في المكان.

أكثر من ذلك، فهي مستقلة تمامًا. خذ على سبيل المثال:

module X
  module Y
  end
end
 
module A
  module B
  end
end
 
module X::Y
  module A::B
    # (3)
  end
end

يتكون التشعب في (3) من كائنات وحدتين:

[A::B, X::Y]

لذلك، لا تنتهي فقط في A، والتي لا تنتمي حتى إلى التشعب، ولكنها تحتوي أيضًا على X::Y، وهي مستقلة عن A::B.

التشعب عبارة عن حزمة داخلية يحتفظ بها المترجم، وتُعدل وفقًا للقواعد التالية:

  • كائن الصنف الذي يتبع الكلمة المفتاحية class يُضَاف عندما ينفذ جسمه، وينتشر (pop) بعد ذلك.
  • كائن الوحدة الذي يتبع الكلمة المفتاحية module يُضَاف عندما ينفذ جسمه، وينشر بعد ذلك.
  • الصنف المنفرد (singleton class) الذي يُفتتَح مع class << object يضاف، ثم ينتشر لاحقًا.
  • عندما يستدعى instance_eval مع سلسلة نصية تمثِّل الوسيط، يُضَاف الصنف المفرد للمتلقي إلى تداخل الشيفرة المقيمة. عندما يستدعى class_eval أو module_eval باستخدام سلسلة وسيطة، يضاف المستقبل إلى تداخل الشيفرة المقيمة.
  • التداخل في المستوى الأعلى من الشيفرة التي فسرت بواسطة Kernel.load يُعدُّ فارغًا، إلا إذا تلقى استدعاء load قيمة صحيحة كوسيط ثانية، وفي هذه الحالة تضاف وحدة مجهولة أُنشئت حديثًا بواسطة روبي.

من المثير للاهتمام ملاحظة أن الكتل لا تعدل المكدس. وعلى وجه الخصوص، فإن الكتل التي قد مُرِّرت إلى Class.new و Module.new لا تجلب الصنف أو الوحدة التي يجري تعريفها والتي أضيفت إلى تشعبها. هذا هو أحد الاختلافات بين تحديد الأصناف والوحدات بطريقة أو بأخرى.

تعريفات الصنف والوحدة هي مهام ثابتة

لنفترض أن الشيفرة البسيطة التالية تنشئ صنفًا (بدلًا من إعادة فتحه):

class C
End

ينشئ روبي الثابت C في Object ويخزن في هذا الثابت على أنه كائن صنف. اسم نسخة الصنف هي "C"، سلسلة نصية، سميت بعد الثابت.

إليك أيضًا الشيفرة التالية:

class Project < ApplicationRecord
End

ينفذ مهمة ثابتة تعادل:

Project = Class.new(ApplicationRecord)

بما في ذلك تحديد اسم الصنف على أنَّه تأثير جانبي:

Project.name # => "Project"

المهمة الثابتة لها قاعدة خاصة لتحقيق ذلك: إذا كان الكائن الذي تعين هو صنف أو وحدة غير معروفة، تعين روبي اسم الكائن إلى اسم الثابت.

تنبيه: من الآن فصاعدًا، ما يحدث للثابت والنسخة لا يهم. على سبيل المثال، يمكن حذف الثابت، أو يمكن تعيين كائن الصنف إلى ثابت مختلف، أو يمكن تخزينه بدون ثابت بعد الآن، ...إلخ. بمجرد تعيين الاسم، لا يتغير.

بالمثل، يجري إنشاء وحدة باستخدام الكلمة المفتاحية module بالشكل التالي:

module Admin
End

تنفيذ مهمة ثابتة تعادل:

Admin = Module.new

بما في ذلك تحديد الاسم على أنَّه تأثير جانبي:

Admin.name # => "Admin"

تحذير: سياق التنفيذ لكتلة مُررت إلى Class.new أو Module.new لا يكافئ تمامًا أحد تعريفات الجسم باستخدام الكلمتين المفتاحيتين class و module. لكن كل من التعابير ينتج عنها نفس المهمة الثابتة.

وهكذا، عندما يقول أحدهم بشكل غير رسمي "الصنف String" ، فهذا يعني بالفعل: كائن الصنف المخزن في الثابت المسمى "String" في كائن الصنف المخزن في الثابت Object. إن String هي ثابت روبي عادي وكل شيء يتعلق بالثوابت مثل خوارزميات الاستبيان ينطبق عليها.

وبالمثل، في وحدة التحكم:

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

لا يعد Post صياغة لصنف. بدلًا من ذلك، Post هو ثابت روبي عادي. إذا كان كل شيء جيد، يقيم الثابت إلى كائن يستجيب إلى all.

هذا هو السبب في أننا نتحدث عن التحميل التلقائي للثابت (constant autoloading)، إذ ريلز لديها القدرة على تحميل الثوابت مباشرةً وتلقائيًّا.

تخزين الثوابت في الوحدات

الثوابت تنتمي إلى وحدات بالمعنى الحرفي. تحتوي الأصناف والوحدات على جدول للثوابت. فكر في الأمر كأنه جدول Hash.

دعونا نحلل مثالًا لفهم ما يعنيه ذلك حقًا. في حين أن الإساءات الشائعة للغة مثل "الصنف String" صحيحة، فإن العرض سيكون دقيقًا هنا لأغراض تعليمية.

لنأخذ بعين الاعتبار تعريف الوحدة التالية:

module Colors
  RED = '0xff0000'
end

أولاً، عند معالجة الكلمة المفتاحية module، ينشئ المترجم قيمة جديدة في جدول الثوابت لكائن الصنف المخزن في الثابت Object. يقترن المدخل بالاسم "Colors" إلى كائن الوحدة التي أُنشئت حديثًا. علاوة على ذلك، يعين المترجم اسم كائن الوحدة الجديد ليكون السلسلة النصية "Colors".

لاحقًا، عندما يُفسر نص تعريف الوحدة، يُنشأ قيمة جديد في جدول الثوابت لكائن الوحدة المخزن في الثابت Colors. تعين تلك القيمة الاسم "RED" إلى السلسلة "0xff0000".

على وجه الخصوص، لا يرتبط Color::RED كليًّا بأي ثابت RED آخر قد يوجد في أي كائن صنف أو وحدة أخرى. إذا كان هناك أي منها، سيكون لديها قيم منفصلة في جداول الثوابت الخاصة بها.

يجب إعطاء اهتمام خاص في الفقرات السابقة للتمييز بين كائنات الصنف والوحدات والأسماء الثابتة وكائنات القيمة المرتبطة بها في جداول الثوابت.

خوارزميات الاستبيان (Resolution Algorithms)

خوارزمية الاستبيان للثوابت النسبية

في أي مكان في الشيفرة، دعنا نعرّف cref ليكون العنصر الأول من التشعب إذا لم يكن فارغًا، أو Object خلاف ذلك.

دون الحصول على الكثير من التفاصيل، فإن خوارزمية الاستبيان لمراجع الثابت النسبي تسير على النحو التالي:

  • إذا لم يكن التشعب فارغًا، فسيُبحث عن الثابت في عناصره بالترتيب. يُتجاهل أسلاف تلك العناصر.
  • إذا لم يُعثر عليه، ستصعد الخوارزمية عبر سلسلة أسلاف cref.
  • إذا لم يُعثر عليه وكانت cref وحدةً، فسيُبحَث عن الثابت في Object.
  • إذا لم يُعثر عليه، يستدعى const_missing على cref. التنفيذ الافتراضي للتابع const_missing يرمي الاستثناء NameError ولكن يمكن تجاوزه.

لا يحاكي التحميل التلقائي لريلز هذه الخوارزمية، ولكن نقطة البداية هي اسم الثابت الذي سيُحمل تلقائيًا، و cref. اطلع على قسم المراجع النسبية للمزيد من التفاصيل.

خوارزمية الاستبيان للثوابت المؤهلة

الثوابت المؤهلة (qualified constants) تبدو كالتالي:

Billing::Invoice

يتكون Billing::Invoice من اثنين من الثوابت: Billing وهو نسبي ويُستبيَن باستخدام خوارزمية القسم السابق.

تنبيه: من شأن النقطتان البارزة أن تجعل الجزء الأول مطلقًا وليس نسبيًا: ::Billing::Invoice. من شأن ذلك أن يجبر Billing على الظهور كثابت عالي المستوى فقط.

Invoice على الجانب الآخر مؤهل من قبل Billing وسنرى استبيانه وقراره فيما يلي. لنحدد الأب ليكون هذا العنصر المؤهل للصنف أو الوحدة، أي Billing في المثال أعلاه. الخوارزمية للثوابت المؤهلة تسير بالشكل التالي:

  • يُبحّث عن الثابت في الأب وأسلافه. في الإصدار 2.5 من ريلز وما بعده، يتخطى Object إذا كان موجودًا بين الأسلاف وستستمر عملية التحقق من Kernel و BasicObject.
  • إذا فشل البحث، يستدعى const_missing في الأب. التنفيذ الافتراضي const_missing يرمي الاستثناء NameError ولكن يمكن تجاوزه.

تنبيه: ما قبل الإصدار 2.5 من ريلز، يقيم String::Hash إلى Hash والمترجم يصدر تحذيرًا: "toplevel constant Hash referenced by String::Hash" (ثابت Hash ذو مستوى أعلى أشير إليه بواسطة String::Hash). بدءًا من الإصدار 2.5، يرمي String::Hash الاستثناء NameError عند تخطي Object.

كما ترى، هذه الخوارزمية أبسط من تلك الثوابت النسبية. على وجه الخصوص، لا يلعب التشعب أي دور هنا، والوحدات ليست حالة خاصة؛ إذا لم تكن لديها هي أو أسلافها الثوابت، لن يُتحقق من Object.

لا يحاكي التحميل التلقائي لريلز هذه الخوارزمية، ولكن نقطة البداية هي اسم الثابت الذي سيحمل تلقائيًا، والأب. اطلع على قسم المراجع الؤهلة للمزيد من التفاصيل.

مفردات اللغة

مجالات أسماء الأب

بالنظر إلى سلسلة نصية ذات مسار ثابت، نحدد مجال اسم الأب الخاص بها لتكون السلسلة التي تنتج عن إزالة الجزء الموجود في أقصى اليمين.

على سبيل المثال ، مجال الاسم للأب من السلسلة "A::B::C" هو السلسلة "A::B"، ومجال الاسم للأب من "A::B" هو "A"، ومجال الاسم للأب من "A " هو "".

تفسير مجال اسم الأب عند التفكير في الأصناف والوحدات أمر صعب رغم ذلك. لنفترض أن الوحدة M تحمل الاسم "A::B":

  • قد لا يعكس مجال الاسم للأب، الذي هو "A"، التشعب في موضع محدد.
  • قد لا يكون الثابت A موجودًا بعد الآن، إذ يكون بعض التعليمات البرمجية قد أزالته من الكائن.
  • إذا كان A موجودًا، فقد لا يكون الصنف أو الوحدة التي كانت في الأصل في A موجودة بعد الآن. على سبيل المثال، إذا كانت هناك مهمة ثابتة أخرى بعد إزالة الثابت، فسيكون هناك كائن مختلف بشكل عام.
  • في مثل هذه الحالة، يمكن أن يحدث أن يعاد تعيين A إلى صنف جديدة أو وحدة تسمى أيضا "A"!
  • في السيناريوهات السابقة، لم يعد من الممكن الوصول إلى M خلال A::B ولكن كائن الوحدة نفسه يمكن أن يبقى على قيد الحياة في مكان ما وسيبقى اسمه "A::B".

تكمن فكرة وجود مجال اسم أب هي عند جوهر خوارزميات التحميل التلقائي والمساعدة على شرح وفهم دوافعها بشكل حدسي، ولكن كما ترى أن الاستعارة تتسرب بسهولة. إذا أخذنا في الاعتبار حالة حدية يجب التفكير فيها، فضع في حسبانك دائمًا أنه عبر "مجال اسم الأب"، الدليل يعني بالضبط اشتقاق سلسلة نصية محددة.

آلية التحميل

تقوم ريلز بالتحميل الآلي للملفات مع Kernel.load عندما تكون قيمة الضبط config.cache_classes هي false، وهو الافتراضي في وضع التطوير، ومع Kernel.require خلاف ذلك، وهو الافتراضي في وضع الإنتاج.

يسمح Kernel.load لريلز بتنفيذ الملفات أكثر من مرة إذا كانت إعادة التحميل للثابت مُفعلة.

يستخدم هذا الدليل الكلمة «تحميل» (load) بحرية للإشارة إلى ترجمة ملف معين، لكن الآلية الفعلية يمكن أن تكون عبر Kernel.load أو Kernel.require اعتمادًا على تلك الراية.

إتاحة التحميل التلقائي

يكون ريلز دائمًا قادرًا على التحميل التلقائي بشرط توفر بيئة التشغيل الخاصة به. على سبيل المثال، الأمر runner التالية يعيد التحميل:

$ bin/rails runner 'p User.column_names'
["id", "email", "created_at", "updated_at"]

وحدة تحكم تتحمل تلقائيًّا، ومجموعة الاختبار تتحمل تلقائيًّا، وبالطبع التطبيق يتحمل تلقائيًّا أيضًا.

بشكل افتراضي، تحمل ريلز بشكل حثيث (eager loads) ملفات التطبيق عند تشغيله في وضع الإنتاج، لذلك لا يحدث معظم التحميل التلقائي هنا الذي يجري في التطوير. ولكن قد لا يزال يُطلَق التحميل التلقائي أثناء التحميل الحثيث.

على سبيل المثال، إعطاء:

class BeachHouse < House
End

إذا كان House لا يزال غير معروف عندما يُحمَّل app/models/beach_house.rb بشكل حثيث، تحمله تلقائيًا.

autoload_paths و eager_load_paths

كما تعلم، عندما تُعطَى require اسم ملف نسبي:

require 'erb'

تبحث روبي عن الملف في المجلدات المدرجة في ‎$LOAD_PATH. هذا هو، تكرر روبي على كافة المجلدات الخاصة به وتتحقق من كل واحد منها من وجود ملف يسمى "erb.rb" أو "erb.so" أو "erb.o" أو "erb.dll". إذا وجدت أي منها، يحملها المترجم وينهي عملية البحث. وإلا، فإنه يحاول مرة أخرى في المجلد التالي من القائمة. إذا نفدت القائمة، فسيُمرمَى الاستثناء LoadError.

سنغطي كيفية عمل التحميل التلقائي للثابت بمزيد من التفاصيل في وقت لاحق، لكن الفكرة هي أنه عندما يكون ثابتًا مثل Post مكتوبًا ومفقودًا، فإذا كان هناك ملفًا باسم post.rb على سبيل المثال في app/models، فسيجده ريلز، ويقيمه، ويحدد Post على أنه تأثير جانبي.

حسنًا، لدى ريلز مجموعة من المجلدات المشابهة لـ ‎$LOAD_PATH للبحث فيها عن post.rb. يطلق على هذه المجموعة الاسم autoload_paths وتتضمن افتراضيًا ما يلي:

  • جميع المجلدات الفرعية للــ app في التطبيق والمحركات الموجودة في وقت الإقلاع. على سبيل المثال، app/controllers. لا تحتاج إلى أن تكون الافتراضية، فكل المجلدات المخصصة مثل app/workers تنتمي تلقائيًا إلى autoload_paths.
  • أي مجلدات موجودة ذات مستوى ثان تسمى app/*/concerns  في التطبيق والمحركات.
  • المجلد test/mailers/previews.

إن eager_load_paths هو في البداية مسارات التطبيق (app) أعلاه.

تعتمد كيفية التحميل التلقائي للملفات على إعدادات تهيئة eager_load و cache_classes التي تختلف عادةً في أوضاع التطوير والإنتاج والاختبار:

  • في التطوير، تريد بدء التشغيل بسرعة مع تحميل تدريجي لشيفرة التطبيق. لذا، يجب يجب تعطيل التحميل الحثيث عبر تعيين eager_load إلى القيمة "false"، وسيحمل ريلز تلقائيًا الملفات حسب الحاجة (انظر قسم خوارزميات التحميل التلقائي أدناه) ثم يعيد تحميلها عند تغييرها (راجع قسم إعادة التحميل للثوابت أدناه).
  • في الإنتاج، ومع ذلك تريد التناسق وسلامة الصفحات ويمكن قبول وقت أطول للإقلاع. لذا، يتعين eager_load إلى القيمة true، وبعد ذلك أثناء الإقلاع (قبل أن يكون التطبيق جاهزًا لاستقبال الطلبات) تحمل ريلز كافة الملفات الموجودة في eager_load_paths ثم توقف التحميل التلقائي (ملحوظة: قد تكون هناك حاجة إلى التحميل التلقائي أثناء التحميل الحثيث). لا يعد التحميل التلقائي بعد التشغيل أمرًا جيدًا، نظرًا لأن التحميل التلقائي يمكن أن يتسبب في حدوث مشكلات في سلامة الصفحات.
  • في الاختبار، لسرعة التنفيذ (من الاختبارات الفردية) eager_load يكون false، لذلك يتبع ريلز سلوك التطوير نفسه.

ما هو موضح أعلاه هي الإعدادات الافتراضية مع تطبيق ريلز أنشأ حديثًا. هناك طرق متعددة يمكن تهيئتها بشكل مختلف (راجع توثيق ضبط تطبيقات ريلز). ولكن استخدام autoload_paths من تلقاء نفسها في الماضي (قبل الإصدار 5) قد يضبط المطورون autoload_paths للإضافة مواقع إضافية (على سبيل المثال lib الذي اعتاد أن يكون قائمة مسار autoload منذ سنوات، ولكن لم يعد كذلك). ومع ذلك، فإن هذا غير محبط الآن لمعظم الأغراض، حيث من المحتمل أن يؤدي إلى أخطاء في الإنتاج فقط. من الممكن إضافة مواقع جديدة إلى كل من config.eager_load_paths و config.autoload_paths ولكن استخدم ذلك على مسؤوليتك الخاصة.

انظر أيضًا قسم التحميل التلقائي في بيئة الاختبار.

الضبط config.autoload_paths غير قابل للتغيير من ملفات الضبط الخاصة بالبيئة.

يمكن فحص قيمة autoload_paths. في تطبيق أنشأ للتو:

$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
.../app/assets
.../app/channels
.../app/controllers
.../app/controllers/concerns
.../app/helpers
.../app/jobs
.../app/mailers
.../app/models
.../app/models/concerns
.../activestorage/app/assets
.../activestorage/app/controllers
.../activestorage/app/javascript
.../activestorage/app/jobs
.../activestorage/app/models
.../actioncable/app/assets
.../actionview/app/assets
.../test/mailers/previews

تنبيه: يُحسَب autoload_paths ويُخزَّن مؤقتًا أثناء عملية التهيئة. يجب إعادة تشغيل التطبيق لتطبيق أي تغييرات في بنية المجلد.

خوارزميات التحميل التلقائي

المراجع النسبية

قد يظهر مرجع ثابت نسبي في عدة أماكن، على سبيل المثال، في:

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

كل المراجع الثلاثة الثابتة نسبية.

الثوابت بعد الكلمتين المفتاحيتين class و module

ينفّذ روبي بحثًا عن الثابت الذي يتبع الكلمة المفتاحية class أو module لأنه يحتاج إلى معرفة ما إذا كان سينشأ الصنف أو الوحدة أو سيُعياد فتحها.

إذا لم يُعرف الثابت عند هذه النقطة، لا يعتبر ثابت مفقودًا ولن يُنفَّذ التحميل التلقائي.

لذا، في المثال السابق، إذا لم يُعرَّف PostController عند ترجمة الملف، لن يُشغل ريلز التحميل التلقائي، سيعمل روبي على تعريف المتحكم فقط.

ثوابت المستوى الأعلى

على العكس من ذلك، إذا كان ApplicationController غير معروف، فسيعتبر الثابت مفقودًا وسيقوم ريلز بمحاولة التحميل التلقائي.

لتحميل ApplicationController، يُكرِّر ريلز عبر المسارات autoload_paths. أولًا تتحقق من وجود app/assets/application_controller.rb. إذا لم تعثر عليه، وهو ما يحدث عادةً، فسيستمر في البحث عن app/controllers/application_controller.rb.

إذا كان الملف يعرّف ApplicationController الثابت، فإن كل شيء على ما يرام، وإلا فسيرمى الاستثناء LoadError:

unable to autoload constant ApplicationController, expected
<full path to application_controller.rb> to define it (LoadError)

تنبيه: لا يتطلب ريلز قيمة الثوابت التي تُحمل تلقائيًا لتكون كائن صنف أو وحدة. على سبيل المثال، إذا كان تطبيق الملف app/models/max_clients.rb يعرّف MAX_CLIENTS = 100، فإن التحميل التلقائي للثابت MAX_CLIENTS يعمل بشكل جيد.

مجالات الأسماء

يبدو التحميل التلقائي للمتحكم ApplicationController مباشرة تحت مجلدات autoload_paths لأن التداخل في تلك البقعة فارغ. يختلف وضع Post، ويكون التشعب في هذا السطر هو [PostsController] ويأتي دور الدعم لمجالات الاسم.

الفكرة الأساسية هي أن إعطاء:

module Admin
  class BaseController < ApplicationController
    @@all_roles = Role.all
  end
end

للتحميل التلقائي لـ Role الذي سيفحص إذا تعرف في مجالات الأسماء الحالية أو المحلية، في وقت واحد. لذا، من الناحية النظرية، نرغب في محاولة التحميل التلقائي لأي منها:

Admin::BaseController::Role
Admin::Role
Role

بهذا الترتيب. هذه هي الفكرة. للقيام بذلك، يبحث ريلز في autoload_paths على التوالي لأسماء الملفات مثل هذه:

admin/base_controller/role.rb
admin/role.rb
Role.rb

سنغطي قريبًا بعض عمليات البحث عن المجلدات الإضافية.

ملاحظة: يعطي 'Constant::Name'.underscore المسار النسبي بدون لاحقة لاسم الملف حيث من المتوقع أن يُحدد Constant::Name.

دعنا نرى كيف يحمل ريلز تلقائيًا الثابت Post في postsController أعلاه بافتراض أن التطبيق يحتوي على النموذج Post المحدد في app/models/post.rb.

أولاً يتحقق من posts_controller/post.rb في autoload_paths:

app/assets/posts_controller/post.rb
app/controllers/posts_controller/post.rb
app/helpers/posts_controller/post.rb
...
test/mailers/previews/posts_controller/post.rb

نظرًا لانتهاء البحث دون نجاح، يُجرَى بحث مشابه عن المجلد، وسنرى السبب في القسم التالي:

app/assets/posts_controller/post
app/controllers/posts_controller/post
app/helpers/posts_controller/post
...
test/mailers/previews/posts_controller/post

إذا فشلت كل هذه المحاولات، فسيبدأ ريلز عملية البحث مرة أخرى في مجال الاسم الأصل. في هذه الحالة، يبقى المستوى الأعلى فقط:

app/assets/post.rb
app/controllers/post.rb
app/helpers/post.rb
app/mailers/post.rb
app/models/post.rb

إذا عُثر على ملف مطابق في app/models/post.rb. يتوقف البحث هناك ويُحمَّل الملف. إذا كان الملف يُعرِّف بالفعل Post، اذا كل شيء على ما يرام، وإلا سيرمى الاستثناء LoadError.

مراجع مؤهلة

عند فقدان ثابت مؤهل، لا يبحث ريلز عنه في مجالات الأسماء للأب. ولكن هناك تحذير: عندما يُفقَد ثابت، لا تستطيع ريلز معرفة ما إذا كان الاطلاق (trigger) مرجعًا نسبيًا أو مرجعًا مؤهلًا.

على سبيل المثال، انظر

module Admin
  User
end

و

Admin::User

إذا كان User مفقودًا، ففي كلتا الحالتين كل ما يعرفه ريلز أنَّ ثابتًا يسمى "User" مفقودٌ في وحدة تسمى "Admin".

إذا كان هناك User ذو مستوى أعلى، ستستبينه روبي في المثال السابق، لكن لن يكون في الأخير. بشكل عام، لا تحاكي ريلز خوارزميات روبي لاستبيان الثوابت، ولكنها في هذه الحالة تحاول استخدام الكشف عن الأشياء التالية:

" إذا كان أي من مجالات أسماء الأب للصنف أو الوحدة يحتوي على ثابت مفقود، يفترض ريلز أن المرجع هو نسبي. خلاف ذلك، يفترض أنه مؤهل. "

على سبيل المثال، إذا كانت هذه الشيفرة تُنفِّذ التحميل التلقائي:

Admin::User

والثابت User موجود بالفعل في Object، ليس من الممكن أنَّ الحالة هي:

module Admin
  User
end

لأنه بخلاف ذلك، كان روبي قد استبين User ولم يكن قد أُجرِي أي تحميل تلقائي في المقام الأول. وبالتالي، يفترض ريلز مرجعًا مؤهلًا ويعتبر الملف admin/user.rb والمجلد admin/user خيارين صالحين فقط.

من الناحية العملية، يعمل هذا بشكل جيد طالما أن التداخل يطابق كافة مجالات أسماء الأب على التوالي وتُعرَف الثوابت التي تجعل القاعدة تنطبق في ذلك الوقت.

ومع ذلك، يحدث التحميل التلقائي عند الطلب. إذا لم يُحمَّل User ذي المستوى الأعلى عن طريق الصدفة، يفترض ريلز مرجعًا نسبيًا بموجب الاتفاق.

إن تسمية التضارب من هذا النوع أمر نادر الحدوث عمليًّا، ولكن إذا حدث ذلك، فإن required_dependency يوفر حلًا من خلال التأكد من أن الثابت المطلوب لبدء تنفيذ الكشف (heuristic) مُعرَّف في المكان التضارب.

الوحدات التلقائية

عندما تعمل وحدة مثل مجال اسم، لا يطلب ريلز التطبيق لتعريف ملف له، إذ المجلد المتطابق لمجال الاسم كافٍ.

لنفترض أن التطبيق يحتوي على مكتب خلفي يُخزن وحدات التحكم فيه في app/controllers/admin. إذا لم تُحمل الوحدة Admin حتى الآن عند الوصول إلى تنفيذ Admin::UsersController، فإن ريلز يحتاج أولًا إلى تحميل الثابت Admin.

إذا كان autoload_paths يحتوي على ملف يسمى admin.rb، سيُحمل ريلز هذا الملف، ولكن إذا لم يكن هناك مثل هذا الملف وعُثر على مجلد يسمى admin، يُنشأ ريلز وحدة فارغة ويُعينه إلى الثابت Admin مباشرةً.

الإجراء العام

تم ذكر فقدان المراجع النسبية في cref حيث سُجلت، وتم ذكر فقدان المراجع المؤهلة في العناصر الأب الخاصة بها (راجع قسم خوارزمية الاستبيان للثوابت النسبية في بداية هذا الدليل لتعريف cref، و خوارزمية الاستبيان للثوابت المؤهلة لتعريف الأب [parent]).

يكون الإجراء للتحميل التلقائي للثابت C في الموقف التعسفي كما يلي:

if the class or module in which C is missing is Object
  let ns = ''
else
  let M = the class or module in which C is missing
 
  if M is anonymous
    let ns = ''
  else
    let ns = M.name
  end
end
 
loop do
  # ابحث عن ملف عادي
  for dir in autoload_paths
    if the file "#{dir}/#{ns.underscore}/c.rb" exists
      load/require "#{dir}/#{ns.underscore}/c.rb"
 
      if C is now defined
        return
      else
        raise LoadError
      end
    end
  end
 
  # ابحث عن وحدة تلقائية
  for dir in autoload_paths
    if the directory "#{dir}/#{ns.underscore}/c" exists
      if ns is an empty string
        let C = Module.new in Object and return
      else
        let C = Module.new in ns.constantize and return
      end
    end
  end
 
  if ns is empty
    # وصلنا إلى المستوى الأعلى دون العثور على الثابت
    raise NameError
  else
    if C exists in any of the parent namespaces
      # كشف الثوابت المؤهلة
      raise NameError
    else
      # حاول مرة أخرى في مجال الاسم للأب
      let ns = the parent namespace of ns and retry
    end
  end
end

require_dependency

يُشغَّل التحميل التلقائي عند الطلب، ولذلك قد يكون التعليمات البرمجية التي تستخدم ثابت معين محددة بالفعل أو قد يؤدي إلى التحميل التلقائي. يعتمد ذلك على مسار التنفيذ وقد يختلف بين عمليات التنفيذ.

ومع ذلك، هناك أوقات تريد فيها التأكد من معرفة ثابت معين عندما يصل التنفيذ إلى رمز ما. يوفر require_dependency طريقة لتحميل ملف باستخدام آلية التحميل الحالية، وتتبع الثوابت المحددة في هذا الملف كما لو تُحملت تلقائيًا لإعادة تحميلها حسب الحاجة.

نادرًا ما نحتاج إلى require_dependency، ولكن اطلع على حالتي الاستخدام المشار إليها في القسم "التحميل التلقائي و STI" التي لا تُشغِّل الثوابت فيها التحميل التلقائي.

تحذير: على عكس التحميل التلقائي، لا تتوقع require_dependency أن يحدد الملف أي ثابت معين. قد يكون استغلال هذا السلوك ممارسة سيئة بالرغم من ذلك، إذ يجب أن تتطابق الملفات والمسارات الثابتة.

إعادة التحميل للثوابت

عندما يكون الضبط config.cache_classes معينًا إلى القيمة false، فإن ريلز قادرة على إعادة تحميل الثوابت المحملة تلقائيًّا.

على سبيل المثال، إذا كنت في جلسة طرفية وقمت بتحرير بعض الملفات من خلف الستار، فيمكن إعادة تحميل الشيفرة مع الأمر reload!‎:

> reload!

عند تشغيل التطبيق، يُعاد تحميل الشفرة عندما يتغير شيء ذي صلة بهذه الشيفرة. من أجل القيام بذلك، تراقب ريلز عدد من الأشياء:

  • config/routes.rb.
  • المحليات (Locales)
  • ملفات روبي ضمن autoload_paths.
  • db/schema.rb و db/structure.sql.

إذا تغير أي شيء هناك، فهناك برنامج وسيط يكتشفها ويعيد تحميل الشيفرة.

يتتبع التحميل التلقائي الثوابت المحملة تلقائيًّا. تُنفذ إعادة التحميل عن طريق إزالتها جميعها من الأصناف والوحدات الخاصة بها باستخدام Module.remove_const. بهذه الطريقة، عندما تُشغل الشيفرة، ستكون هذه الثوابت غير معروفة مرة أخرى، ويُعاد تحميل الملفات عند الطلب.

تنبيه: هذه عملية كل شيء أو لا شيء، إذ لا يحاول ريلز إعادة تحميل سوى ما تغير لأن التبعيات بين الطبقات يجعل ذلك أمرٌ محالٌ حقًا. بدلًا من ذلك، يُمسح كل شيء.

التابع Module.autoload غير مضمن

يوفر التابع Module.autoload طريقة كسولة لتحميل الثوابت التي تتكامل تمامًا مع خوارزميات البحث لثوابت روبي، وواجهة برمجة التطبيقات الثابتة الديناميكية، ...إلخ. إن شفاف تمامًا.

تستخدم ريلز داخليًّا ذلك التابع استخدامًا واسع النطاق لتأجيل أكبر قدر ممكن من العمل المراد تنفيذه في عملية الإقلاع. ولكن التحميل التلقائي للثوابت لايُنفذ في ريلز مع Module.autoload.

أحد التنفيذات الممكنة التي تستند إلى Module.autoload هو التدرج في شجرة التطبيق وإصدار استدعاءات autoload التي تُعين أسماء الملفات الموجودة إلى اسمها الثابت التقليدي.

هناك عدد من الأسباب التي تمنع ريلز من استخدام هذا التنفيذ.

على سبيل المثال، لا يستطيع Module.autoload  إلا تحميل الملفات باستخدام require، لذا لن تكون عملية إعادة التحميل ممكنة. ليس ذلك فحسب، بل يستخدم require الداخلي الذي يختلف عن Kernel.require.

بعد ذلك، فإنه لا يوفر أية طريقة لإزالة التعريفات في حالة حذف ملف. في حالة إزالة الثابت باستخدام Module.remove_const، لا يستدعى autoload الخاص به مرة أخرى. كما أنه لا يدعم الأسماء المؤهلة، لذلك يجب ترجمة الملفات ذات مجالات الأسماء أثناء التدرج في الشجرة لتثبيت استدعاءات autoload الخاصة، لكن هذه الملفات قد تحتوي على مراجع ثابتة لم تُضبَط بعد.

سيكون التنفيذ الذي يعتمد على Module.autoload رائعًا لكن، كما ترى، على الأقل اعتبارًا من اليوم غير ممكن. يُنفَّذ التحميل التلقائي في ريلز مع Module.const_missing وهذا هو السبب في أنه يحتوي على اتفاقية خاص به، موثقة في هذا الدليل.

مشاكل شائعة

التشعب والثوابت المؤهلة

افترض أن لدينا الشيفرة التالية:

module Admin
  class UsersController < ApplicationController
    def index
      @users = User.all
    end
  end
end

و

class Admin::UsersController < ApplicationController
  def index
    @users = User.all
  end
end

لاستبيان User، يتحقق روبي من Admin في الحالة السابقة، لكنه غير موجود في الأخير لأنه لا ينتمي إلى التشعب (راجع قسم التشعب وخوارزميات الاستبيان).

لسوء الحظ، لا يعرف التحميل التلقائي لريلز التشعب في المكان الذي كان فيه الثابت مفقودًا، وبالتالي لا يكون قادرًا على التصرف كما يفعل روبي. على وجه الخصوص، سيُحمَّل Admin::User تلقائيًّا في كلتا الحالتين.

على الرغم من أن الثوابت المؤهلة التي تحتوي على الكلمتين المفتاحيتين class و module قد تعمل تقنيًا مع التحميل التلقائي في بعض الحالات، فمن الأفضل استخدام الثوابت النسبية بدلًا من ذلك:

module Admin
  class UsersController < ApplicationController
    def index
      @users = User.all
    end
  end
end

التحميل التلقائي و STI

توريث الجدول الواحد (STI) هي إحدى ميزات Active Record التي تمكّن من تخزين تسلسل هرمي للنماذج في جدول واحد. واجهة برمجة التطبيقات (API) لهذه النماذج تدرك التسلسل الهرمي وتلخص بعض الاحتياجات العامة. على سبيل المثال، بالنظر إلى هذه الأصناف:

# app/models/polygon.rb
class Polygon < ApplicationRecord
end
 
# app/models/triangle.rb
class Triangle < Polygon
end
 
# app/models/rectangle.rb
class Rectangle < Polygon
end

ينشئ Triangle.create صفًا (row) يمثل المثلث، وينشئ Rectangle.create صفًا يمثل مستطيلًا. إذا كان المُعرِّف (id) هو المُعرِّف ID لسجل موجود، فسيعيد (Polygon.find(id كائنًا من النوع الصحيح.

التوابع التي تعمل على مجموعات هي أيضًا على بينة من التسلسل الهرمي. على سبيل المثال، يعيد Polygon.all كافة سجلات الجدول، لأن كافة المستطيلات والمثلثات عبارة عن مضلعات. يعتني Active Record بإعادة نسخ من الصنف المقابل لها في مجموعة النتائج.

تُحمَّل الأنواع تلقائيًّا حسب الحاجة. على سبيل المثال، إذا كان Polygon.first مستطيلًا ولم يُحمَّل Rectangle بعد، يحمله Active Record تلقائيًا وينشئ السجل بشكل صحيح.

كل شيء جيد، ولكن إذا أردنا العمل على بعض الأصناف الفرعية، بدلًا من تنفيذ استعلامات تستند إلى صنف الجذر، فستصبح الأشياء مثيرة للاهتمام.

أثناء العمل مع Polygon، لا تحتاج إلى أن تكون على دراية بجميع أحفاده، لأن أي شيء في الجدول يكون مضلعًا بحكم التعريف، ولكن عند العمل مع الأصناف الفرعية يجب أن يكون Active Record قادرًا على تعداد الأنواع التي يبحث عنها. دعونا نرى مثالًا عن ذلك.

يُحمل Rectangle.all المستطيلات فقط عن طريق إضافة قيد نوع إلى الاستعلام:

SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")

دعونا نقدم الآن صنفًا فرعيًّا من Rectangle:

# app/models/square.rb
class Square < Rectangle
end

يجب أن يعيد Rectangle.all الآن المستطيلات (rectangles) والمربعات (squares):

SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle", "Square")

ولكن هناك تحذير هنا: كيف يعرف Active Record أن الصنف Square موجودة حتمًا؟ حتى إذا كان الملف app/models/square.rb موجودًا ويعرِّف الصنف Square، إذا لم تستخدم أي شيفرة بعد ذلك الصنف، فإن Rectangle.all يصدر الاستعلام.

SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")

هذا ليس خطأ، ويشمل الاستعلام جميع الأحفاد المعروفة من Rectangle.

طريقة للتأكد من أن هذا يعمل بشكل صحيح بغض النظر عن ترتيب التنفيذ هي تحميل الأصناف الفرعية المباشرة يدويًّا في أسفل الملف الذي يعرّف كل صنف وسيط (intermediate class):

# app/models/rectangle.rb
class Rectangle < Polygon
end
require_dependency 'square'

يجب أن يحدث هذا لكل صنف وسيط (ليس جذرًا [root] وليس ورقةً [leaf]). لا يوسع نطاق الجذر نطاق البحث حسب النوع، وبالتالي لا يلزم بالضرورة معرفة كل أحفاده.

التحميل التلقائي والطلب عبر require

لا يجب مطلقًا طلب الملفات التي تعرِّف الثوابت المراد تحميلها تلقائيًا:

require 'user' # لا تفعل هذا
 
class UsersController < ApplicationController
  ...
end

هناك نوعان من المشكلات المحتملة هنا في وضع التطوير:

1- إذا جرى تحميل User تلقائيًا قبل الوصول إلى require، فسيُشغَّل app/models/user.rb مرة أخرى نظرًا لأن التحميل لا يُحدّث ‎$LOADED_FEATURES.

2- إذا نُفِّذ require أولًا، فإن ريلز لا يضع علامة على User على أنه ثابت محمَّل تلقائيًّا ولا يُعاد تحميل التغييرات في app/models/user.rb.

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

وبالطبع، فإن استخدام ما يتطلب من ملفات حُمِّلَت تلقائيًا لتحميل مكتبات عادية تابعة لجهات خارجية أمرٌ جيد، وريلز قادرة على تمييز ثوابتها، ولا يُوضع علامة عليها بأنها جرى تحميلها تلقائيًّا.

التحميل التلقائي و المهيأت   

خذ بعين الاعتبار هذه المهمة في config/initializers/set_auth_service.rb:

AUTH_SERVICE = if Rails.env.production?
  RealAuthService
else
  MockedAuthService
end

قد يكون الغرض من هذا الإعداد أن يستخدم التطبيق الصنف الذي يتوافق مع البيئة عبر AUTH_SERVICE. في وضع التطوير، يحمَّل MockedAuthService تلقائيًّا عند تشغيل المُهيئ. لنفترض أننا نقوم ببعض الطلبات، ونغير تنفيذها، ونعود للتطبيق مرة أخرى لنندهش بعدم انعكاس التغييرات في تطبيقنا. لماذا؟

كما رأينا سابقًا، يزيل ريلز التحميل التلقائي للثوابت، ولكن يخزن AUTH_SERVICE كائن الصنف الأصلي. استخدام الثابت الأصلي هو سلوك عفا عليه الزمن وغير قابل للوصول ولكنه فعال وعملي بشكل ممتاز.

تلخص الشيفرة التالية الحالة:

class C
  def quack
    'quack!'
  end
end
 
X = C
Object.instance_eval { remove_const(:C) }
X.new.quack # => quack!
X.name      # => C
C           # => uninitialized constant C (NameError)

وبسبب ذلك، ليست فكرة التحميل التلقائي للثوابت جيدة عند تهيئة التطبيق.

في الحالة أعلاه، يمكننا تنفيذ نقطة وصول ديناميكية:

# app/models/auth_service.rb
class AuthService
  if Rails.env.production?
    def self.instance
      RealAuthService
    end
  else
    def self.instance
      MockedAuthService
    end
  end
end

ويتوجب على التطبيق استخدام AuthService.instance بدلًا من ذلك. ستُحمل AuthService عند الطلب.

التابع required_dependency والمهيآت

كما رأينا من قبل، يحمِّل need_dependency الملفات بطريقة جيدة. ومع ذلك، عادةً ما لا يكون مثل هذا الاستدعاء منطقيًا في المُهيئ.

يمكن للمرء أن يفكر في إجراء بعض استدعاءات require_dependency في المُهيئ للتأكد من تحميل ثوابت معينة مقدمًا، على سبيل المثال كمحاولة لمعالجة مشكلات مع توريثات الجدول الواحد (STIs).

المشكلة هي، في وضع التطوير تُمسَح الثوابت المحملة تلقائيًّا إذا كان هناك أي تغيير ذي صلة في نظام الملفات. إذا حدث ذلك، فنحن في نفس الموقف الذي أراد المهيئ تجنبه!

الإستدعاء إلى require_dependency يجب أن يكون مكتوب بشكل استراتيجي في مواقع التحميل التلقائي.

عندما لا تكون الثوابت مفقودة

المراجع النسبية

دعونا ننظر في جهاز محاكاة الطيران. يحتوي التطبيق على نموذج رحلة (flight model) افتراضي:

# app/models/flight_model.rb
class FlightModel
end

يمكن استبدالها من قبل كل طائرة، على سبيل المثال:

# app/models/bell_x1/flight_model.rb
module BellX1
  class FlightModel < FlightModel
  end
end
 
# app/models/bell_x1/aircraft.rb
module BellX1
  class Aircraft
    def initialize
      @flight_model = FlightModel.new
    end
  end
end

يريد المُهيئ إنشاء BellX1::FlightModel وتداخل يحتوي على BellX1، الذي يبدو جيدًا إلى الآن. ولكن إذا حُمِّل نموذج الطيران الافتراضي (default flight model) ولم يُحمَّل النموذج Bell-X1، فسيكون بمقدور المترجم استبيان FlightModel ذي المستوى الأعلى وبالتالي لا يُشغل ميزة التحميل التلقائي لـ BellX1::FlightModel.

تعتمد تلك الشيفرة على مسار التنفيذ.

يمكن في الغالب استبيان هذا النوع من الغموض باستخدام ثوابت مؤهلة:

module BellX1
  class Plane
    def flight_model
      @flight_model ||= BellX1::FlightModel.new
    end
  end
end

أيضا، require_dependency هو الحل:

require_dependency 'bell_x1/flight_model'
 
module BellX1
  class Plane
    def flight_model
      @flight_model ||= FlightModel.new
    end
  end
end

مراجع مؤهلة

تحذير: هذه المشكلة يُحتمَل حدوثها في الإصدارات التي قبل 2.5 من روبي.

خذ مثلًا الشيفرة التالية:

# app/models/hotel.rb
class Hotel
end
 
# app/models/image.rb
class Image
end
 
# app/models/hotel/image.rb
class Hotel
  class Image < Image
  end
end

التعبير Hotel::Image غامض لأنه يعتمد على مسار التنفيذ.

كما رأينا من قبل، يبحث روبي عن الثابت في Hotel وأسلافه. إذا حُمِّل app/models/image.rb ولكن لم يُحمَّل app/models/hotel/image.rb، لن يعثر روبي على Image في Hotel، لكنه سيعثر عليها في Object:

$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null
Image # Hotel::Image وليس

يجب أن يكون تقييم الشيفرة Hotel::Image متأكدًا من تحميل app/models/hotel/image.rb، ربما مع require_dependency.

في هذه الحالات، يصدر المترجم تحذيرًا على الرغم من ذلك:

warning: toplevel constant Image referenced by Hotel::Image

يمكن ملاحظة هذا الاستبيان الثابت المذهل مع أي صنف مؤهل:

2.1.5 :001 > String::Array
(irb):1: warning: toplevel constant Array referenced by String::Array
 => Array

تحذير: للعثور على هذه المشكلة، يجب أن يكون مجال الاسم المؤهل صنفًا، إذ Object ليس سلف للوحدات.

التحميل التلقائي داخل الأصناف المفردة

لنفترض أن لدينا تعريفات الصنف التالية:

# app/models/hotel/services.rb
module Hotel
  class Services
  end
end
 
# app/models/hotel/geo_location.rb
module Hotel
  class GeoLocation
    class << self
      Services
    end
  end
end

إذا كان Hotel::Services معروف في وقت تحميل app/models/hotel/geo_location.rb، يُستبيَن Services بواسطة روبي لأن Hotel ينتمي إلى التشعب عندما يُفتَح صنف منفرد (singleton class) من Hotel::GeoLocation.

ولكن إذا كان Hotel::Services غير معروف، تكون ريلز غير قادرة على التحميل التلقائي، ويرمي التطبيق الاستثناء NameError.

السبب في ذلك هو أن التحميل التلقائي يُشغَّل لصنف منفرد، الذي هو مجهول، وكما رأينا من قبل، تتحقق ريلز فقط من مجال الاسم الأعلى في تلك الحالة الحدية.

الحل السهل لهذا التحذير هو تأهيل الثابت:

module Hotel
  class GeoLocation
    class << self
      Hotel::Services
    end
  end
end

التحميل التلقائي في BasicObject

الأحفاد المباشرة من BasicObject لا تملك Object بين أسلافها ولا يمكن استبيان الثوابت ذات المستوى الأعلى:

class C < BasicObject
  String # NameError: uninitialized constant C::String
end

عندما يشترك التحميل التلقائي، تنحرف تلك المشكلة وتأخذ منحًا آخر. لنفترض ما يلي:

class C < BasicObject
  def user
    User # خطأ
  end
end

نظرًا لأن ريلز يتحقق من مجال الاسم ذي المستوى الأعلى، يُحمَّل User تلقائيًّا بشكل جيد في المرة الأولى التي يستدعى فيها التابع User. تحصل على الاستثناء فقط إذا كان الثابت User معروفًا عند هذه النقطة، خاصة في الاستدعاء الثاني لـ user:

c = C.new
c.user # surprisingly fine, User
c.user # NameError: uninitialized constant C::User

لأنه يكتشف أن مجال الاسم للأب يحتوي على الثابت (انظر قسم مراجع مؤهلة).

كما هو الحال مع روبي الصافي، داخل جسم السلف المباشر من BasicObject، استخدم دائمًا المسارات الثابتة المطلقة:

class C < BasicObject
  ::String # RIGHT
 
  def user
    ::User # RIGHT
  end
end

التحميل التلقائي في بيئة الاختبار

عند تهيئة بيئة الاختبار (test environment) للتحميل التلقائي، يمكنك التفكير في عدة عوامل.

على سبيل المثال، قد يكون من المفيد تشغيل اختباراتك باستخدام إعداد مماثل للإنتاج (config.eager_load = true، و config.cache_classes = true) من أجل اكتشاف أي مشاكل قبل أن تنشر على بيئة الإنتاج (وهذا تعويض عن عدم وجود تعادل dev-prod ). ومع ذلك، فإن هذا سيبطئ وقت الإقلاع للاختبارات الفردية على جهاز التطوير (dev machine، وهو غير متوافق على الفور مع spring، فانظر أدناه). لذا فإن أحد الاحتمالات هو القيام بذلك على جهاز CI فقط (والذي يجب أن يعمل دون spring).

يمكنك بعد ذلك، على جهاز التطوير، تشغيل الاختبارات الخاصة بك مع أي شيء أسرع (بشكل مثالي config.eager_load = false).

باستخدام أداة التحميل المسبق من Spring (المضمنة في تطبيقات ريلز الجديدة)، ستحتفظ بشكل مثالي عبر ضبط config.eager_load = false وفقًا للتطوير. في بعض الأحيان قد ينتهي بك الأمر مع ضبط مختلط:

config.eager_load = true, config.cache_classes = true AND config.enable_dependency_loading = true

راجع مشكلة Spring. ومع ذلك، قد يكون من الأسهل الحفاظ على نفس الضبط مثل التطوير، اكتشف حلًا لكل سبب يؤدي إلى فشل التحميل التلقائي (ربما عن طريق نتائج اختبار CI الخاص بك).

قد تحتاج من حين لآخر إلى التحميل الحثيث عبر eager_load صراحةً باستخدام Rails.application.eager_load!‎ في إعداد اختباراتك، إذ قد يحدث هذا إذا كانت اختباراتك تتضمن عدة خيوط.

مصادر