الشروع في العمل مع المحركات في ريلز

من موسوعة حسوب
مراجعة 16:24، 22 مارس 2019 بواسطة جميل-بيلوني (نقاش | مساهمات) (مراجعة وتدقيق)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

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

بعد قراءة هذا الدليل، ستتعلم:

  • مما يتكون المحرك.
  • كيف وُلِّد المحرك.
  • كيفية بناء ميزات للمحرك.
  • كيفية ربط المحرك في التطبيق.
  • كيفية استبدال وظيفة المحرك في التطبيق.
  • تجنب تحميل أطر ريلز مع خطافات التحميل (Load Hooks) وخطافات الضبط (Configuration Hooks).

ما هي المحركات؟

تُعدُّ المحركات تطبيقات مصغرة توفر وظائف لتطبيقاتها المضيفة. تطبيق ريلز هو بالفعل محرك "supercharged"، مع الصنف Rails::Application الذي يرث الكثير من سلوكه من Rails::Engine.

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

ترتبط المحركات أيضًا ارتباطًا وثيقًا بالمكونات الإضافية. يشترك الاثنان في بنية المجلد lib، وكلاهما يُنشَأ باستخدام المولِّد rails plugin new. الفرق هو أن المحرك يعتبر "إضافة كاملة" (full plugin) بواسطة ريلز (كما هو مشار إليه في الخيار ‎--full الذي يُمررإلى أمر المولد).

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

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

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

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

لمشاهدة عروض محركات أخرى، تحقق من Devise، وهو محرك يوفر الاستيثاق لتطبيقاته الرئيسية، أو Thredded، وهو محرك يوفر وظائف منتدى (forum). هناك أيضا Spree الذي يوفر منصة تجارة إلكترونية، و Refinery CMS، الذي يعد محرك نظام إدارة محتوى (CMS).

وأخيرًا، لم تكن المحركات ممكنة بدون أعمال James Adam و Piotr Sarnacki وفريق ريلز الأساسي وعدد آخر من الأشخاص. إذا قابلتهم في أي وقت، لا تنسَ أن تقول لهم شكرًا!

توليد محرك

لتوليد محركٍ، سوف تحتاج لتشغيل المولد plugin وتمرير الخيارات بما يتناسب مع الحاجة. لتوليد المثال "blorgh"، ستحتاج إلى إنشاء محرك "قابل للوصل" (mountable)، وتشغيل هذا الأمر في طرفية:

$ rails plugin new blorgh --mountable

يمكن الاطلاع على القائمة الكاملة لخيارات المولد plugin عن طريق الأمر التالي:

$ rails plugin --help

يخبر الخيار mountable-- المولد بأتك تريد إنشاء محرك "قابل للوصل" (mountable) وذي مجال اسم معزول (namespace-isolated). هذا المولد سيوفر نفس الهيكلية كما هو الحال مع الخيار ‎--full. يخبر الخيار ‎--full المولد بأنك تريد إنشاء محركٍ يتضمن البنية الهيكلية التالية:

  • شجرة المجلد app.
  • الملف config/routes.rb:
‎‎Rails.application.routes.draw do
end
  • ملفٌ في lib/blorgh/engine.rb، وهو يماثل وظيفة الملف config/application.rb في تطبيق ريلز القياسي:
module Blorgh
  class Engine < ::Rails::Engine
  end
end

سيضيف الخيار ‎--mountable إلى الخيار ‎--full ما يلي:

  • ملفات بيان الأصول (application.js و application.css).
  • أصل ذو مجال الاسم ApplicationController.
  • أصل ذو مجال الاسم ApplicationHelper.
  • مخطط قالب العرض للمحرك.
  • مجال اسم معزول إلى config/routes.rb:
Blorgh::Engine.routes.draw do
End
  • مجال اسم معزول إلى lib/blorgh/engine.rb:
module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
  end
end

بالإضافة إلى ذلك، يخبر الخيار ‎--mountable المولد أن يوصل المحرك داخل تطبيق الاختبار الوهمي الموجود في test/dummy بإضافة ما يلي إلى ملف مسارات التطبيق الوهمي في test/dummy/config/routes.rb:

mount Blorgh::Engine => "/blorgh"

داخل المحرك

الملفات الحرجة

في المجلد الجذر، يولد الملف blorgh.gemspec ضمن المجلد engines الجديد. عندما تضمِّن المحرك في التطبيق لاحقًا، ستستخدم السطر التالي في الملف Gemfile الخاص بتطبيق ريلز:

gem 'blorgh', path: 'engines/blorgh'

لا تنسَ تشغيل الأمر bundle install كالمعتاد. من خلال تحديده كجوهرة داخل Gemfile، سيحمله المُحزِّم (Bundler) على هذا النحو، مع تحليل الملف blorgh.gemspec هذا وطلب ملف يدعى lib/blorgh.rb داخل المجلد lib. هذا الملف يتطلب الملف blorgh/engine.rb (الموجود في lib/blorgh/engine.rb) ويُعرِّف وحدة أساسية تسمى Blorgh.

require "blorgh/engine"
 
module Blorgh
end

ملاحظة: تختار بعض المحركات استخدام هذا الملف لوضع خيارات التهيئة العامة لمحركها. إنها فكرة جيدة نسبيًا، لذلك إذا كنت ترغب في توفير خيارات الضبط، فإن الملف حيث عُرِّفَ module الخاص بمحركك فيه مثالي لذلك. ضع التوابع داخل الوحدة وستكون على ما يرام.

يقبع ضمن lib/blorgh/engine.rb الصنف الأساسي للمحرك:

module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
  end
end

من خلال الوراثة من الصنف Rails::Engine، تخطر الجوهرة ريلز بوجود محرك في المسار المحدد، وستوصل المحرك داخل التطبيق بشكل صحيح، وتُنفذ مهامًا مثل إضافة مجلد التطبيق الخاص بالمحرك إلى مسار التحميل للنموذج والبريد ووحدة التحكم والواجهات.

إنَّ التابع isolate_namespace هنا يستحق بعض الانتباه. هذا الاستدعاء مسؤول عن عزل وحدات التحكم والنماذج والمسارات وغيرها من الأشياء في مجال الاسم الخاص بهم، بعيدًا عن المكونات المتشابهة داخل التطبيق.

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

ملاحظة: يوصى بشدة أن يترك سطر isolate_namespace داخل تعريف الصنف Engine. بدونه، قد تتضارب الأصناف التي أُنشئت في المحرك مع أحد التطبيقات.

ماذا تعني هذه العزلة في مجال الاسم هي أنَّ النموذج الذي أُنشِئ بواسطة استدعاء bin/rails g model، مثل bin/rails g model article، لن يُسمى Article، ولكن سيكون بدلًا من ذلك مجال اسم يدعى Blorgh::Article. بالإضافة إلى ذلك، يُوضع جدول للنموذج في مجال اسم بدلًا من ذلك، ليصبح blorgh_articles، بدلًا من مجرد articles. وبشكل مماثل لنموذج مجال الأسماء، وحدة تحكم التي تسمى ArticlesController تُصبح Blorgh::ArticlesController، والواجهات لوحدة التحكم لن تكون app/views/articles ولكن ستكون بدلًا من ذلك app/views/blorgh/articles. مرسلي البريد (Mailers) توضع ضمن مجال اسمٍ أيضًا بالأسلوب نفسه أيضًا.

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

المجلد app

يوجد داخل المجلد app المجلدات assets و controllers و helpers و mailers و models و views القياسية التي يجب أن تكون على دراية بها من أحد التطبيقات. تكون المجلدات helpers و mailers و models فارغةً، لذا لن تُشرَح في هذا القسم. سننظر أكثر في النماذج (models) في قسم لاحق، عندما نكتب المحرك.

ضمن المجلد app/assets، توجد المجلدات images و javascripts و stylesheets التي يجب أن تكون، مرة أخرى، على دراية بها بسبب تشابهها مع أي تطبيق. لكن أحد الاختلافات هنا هو أن كل مجلد يحتوي على مجلد فرعي باسم المحرك. نظرًا لأن هذا المحرك سيوضع ضمن مجال اسم، يجب أن توضع أصوله أيضًا.

يوجد ضمن المجلد app/controllers المجلد blorgh الذي يحتوي على ملف يسمى application_controller.rb. سيوفر هذا الملف أي وظائف مشتركة لوحدات التحكم في المحرك. المجلد blorgh هو المكان الذي سوف تذهب إليه وحدات التحكم الأخرى للمحرك. بوضعها ضمن هذا المجلد الذي يملك مجال اسم، فإنك تمنعهم من التضارب مع وحدات التحكم المسماة باسم مماثل داخل محركات أخرى أو حتى داخل التطبيق.

ملاحظة: يُسمى الصنف ApplicationController داخل المحرك مثل أي تطبيق ريلز لتسهيل عملية تحويل التطبيقات إلى محركات.

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

# app/controllers/blorgh/articles_controller.rb:
require_dependency "blorgh/application_controller"
 
module Blorgh
  class ArticlesController < ApplicationController
    ...
  end
end

تحذير: لا تستعمل require لأنها ستفشل إعادة التحميل التلقائي للأصناف في بيئة التطوير. استعمال require_dependency يضمن أن تُحمَّل الأصناف وتفرَّغ بالطريقة الصحيحة.

وأخيرا، يحتوي المجلد app/views على المجلد layouts، والذي يحتوي على ملف في blorgh/application.html.erb. يتيح لك هذا الملف تحديد تخطيط للمحرك. إذا كان سيُستخدَم هذا المحرك كمحرك مستقل (stand-alone engine)، فستضيف أي تخصيص لتخطيطه في هذا الملف بدلًا من ملف التطبيق app/views/layouts/application.html.erb.

إذا كنت لا ترغب في فرض تخطيط على مستخدمي المحرك، يمكنك حذف هذا الملف والإشارة إلى تخطيط مختلف في وحدات التحكم في المحرك الخاص بك.

المجلد bin

يحتوي هذا المجلد على ملف واحد، bin/rails، والذي يمكّنك من استخدام أوامر rails الفرعية والمولدات مثلما تفعل داخل أي تطبيق ريلز. هذا يعني أنك ستتمكن من إنشاء وحدات تحكم ونماذج جديدة لهذا المحرك بسهولة بالغة عن طريق تشغيل الأوامر مثل:

$ bin/rails g model

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

المجلد test

المجلد test هو المكان الذي ستذهب إليه اختبارات المحرك. لاختبار المحرك، هناك نسخة مختصرة من تطبيق ريلز مضمَّن فيه في test/dummy. سيوصل (mount) هذا التطبيق المحرك في الملف test/dummy/config/routes.rb:

Rails.application.routes.draw do
  mount Blorgh::Engine => "/blorgh"
end

يقوم هذا السطر بوصل المحرك على المسار ‎/blorgh، مما يجعل الوصول إليه ممكنًا من خلال التطبيق على هذا المسار فقط.

يوجد داخل المجلد test المجلد test/integration، حيث يجب وضع اختبارات التكامل للمحرك. يمكن إنشاء مجلدات أخرى في المجلد test أيضًا. على سبيل المثال، قد ترغب في إنشاء المجلد test/models لاختبارات نموذجك.

توفير وظائف المحرك

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

توليد مصادر للمقال

أول شيء يراد توليده لمحرك مدونة هو النموذج Article ووحدة التحكم ذات الصلة. لتوليد ذلك بسرعة، يمكنك استخدام المولد scaffold في ريلز بالشكل التالي:

$ bin/rails generate scaffold article title:string text:text

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

invoke  active_record
create    db/migrate/[timestamp]_create_blorgh_articles.rb
create    app/models/blorgh/article.rb
invoke    test_unit
create      test/models/blorgh/article_test.rb
create      test/fixtures/blorgh/articles.yml
invoke  resource_route
 route    resources :articles
invoke  scaffold_controller
create    app/controllers/blorgh/articles_controller.rb
invoke    erb
create      app/views/blorgh/articles
create      app/views/blorgh/articles/index.html.erb
create      app/views/blorgh/articles/edit.html.erb
create      app/views/blorgh/articles/show.html.erb
create      app/views/blorgh/articles/new.html.erb
create      app/views/blorgh/articles/_form.html.erb
invoke    test_unit
create      test/controllers/blorgh/articles_controller_test.rb
invoke    helper
create      app/helpers/blorgh/articles_helper.rb
invoke  test_unit
create    test/application_system_test_case.rb
create    test/system/articles_test.rb
invoke  assets
invoke    js
create      app/assets/javascripts/blorgh/articles.js
invoke    css
create      app/assets/stylesheets/blorgh/articles.css
invoke  css
create    app/assets/stylesheets/scaffold.css

أول شيء يستدعيه المولد scaffold هو المولد active_record، الذي يولد تهجيرًا (migration) ونموذجًا للمصدر. ومع ذلك، لاحظ أنَّ التهجير يدعى create_blorgh_articles بدلًا من create_articles المتوقع. هذا بسبب التابع isolate_namespace الذي استدعي في تعريف الصنف Blorgh::Engine. وأضيف اسم النموذج هنا ايضًا إلى مجال اسم، حيث يُوضع في app/models/blorgh/article.rb بدلًا من app/models/article.rb بسبب استدعاء isolate_namespace داخل الصنف Engine.

بعد ذلك، يُستدعَى المولد test_unit لهذا النموذج، مما يؤدي إلى توليد اختبار لنموذج في test/models /blorgh/article_test.rb (بدلًا من test/models/article_test.rb) وتحضير اختبار في test/fixtures/blorgh/articles.yml (بدلًا من test/fixtures/articles.yml).

بعد ذلك، يُدرج سطر للمصدر في الملف config/routes.rb للمحرك. هذا السطر هو ببساطة resources :articles، وتحويل الملف config/routes.rb للمحرك إلى ما يلي:

Blorgh::Engine.routes.draw do
  resources :articles
end

لاحظ هنا أن المسارات الموجهة تعتمد على الكائن Blorgh::Engine بدلًا من الصنف YourApp::Application. هذا هو سبب أن مسارات التوجيه للمحرك تقتصر على المحرك نفسه ويمكن وصلها في نقطة محددة كما هو موضح في قسم «المجلد test». كما يتسبب في عزل مسارات التوجيه للمحرك عن تلك المسارات الموجودة داخل التطبيق. يصف قسم مسارات التوجيه من هذا الدليل ذلك بالتفصيل.

بعد ذلك، يُستدعَى المولد scaffold_controller، وتُولد وحدة تحكم تسمى Blorgh::ArticlesController (في app/controllers/blorgh/articles_controller.rb) والواجهات ذات الصلة في app/views/blorgh/articles.

هذا المولد يولد أيضا اختبارًا لوحدة التحكم (test/controllers/blorgh/articles_controller_test.rb) ومساعدًا (app/helpers/blorgh/articles_helper.rb).

كل شيء أنشأه هذا المولد يكون ضمن مجال اسم. وعُرِّف صنف وحدة التحكم في الوحدة Blorgh:

module Blorgh
  class ArticlesController < ApplicationController
    ...
  end
end

ملاحظة: يرث الصنف ArticlesController من Blorgh::ApplicationController، وليس من ApplicationController الخاص بالتطبيق.

يكون اسم المساعد داخل app/helpers/blorgh/articles_helper.rb ضمن مجال أسماء أيضًا:

module Blorgh
  module ArticlesHelper
    ...
  end
end

يساعد ذلك على منع التعارضات مع أي محرك أو تطبيق آخر قد يكون له مصدر مقالة (article resource) أيضًا.

وأخيرًا، يُنشأ أصول لهذا المصدر في ملفين: app/assets/javascripts/blorgh/articles.js و app/assets/stylesheets/blorgh/articles.css. سترى كيفية استخدام هذه بعد قليل.

يمكنك أن ترى ما الذي يملكه المحرك حتى الآن عبر تنفيذ الأمر bin/rails db:migrate في جذر المحرك الخاص بنا لتنفيذ التهجير المُولَّد عبر المولِّد scaffold، ثم تنفيذ الأمر rails server في test/dummy. عند فتح الرابط http://localhost:3000/blorgh/articles في المتصفح، سترى ما الذي وُلِّد حتى الآن. لقد ولَّدت إلى الآن الدفعة الأولى من الدوال لأول محرك خاص بك.

إذا كنت تفضل اللعب في وحدة التحكم عوضًا عن ذلك، فسيعمل الأمر rails console أيضًا مثل عمله في أي تطبيق ريلز. تذكر أن النموذج Article قابع ضمن مجال اسم، لذلك أخذ ذلك بالحسبان للإشارة إليه مثل Blorgh::Article.

>> Blorgh::Article.find(1)
=> #<Blorgh::Article id: 1 ...>

شيء واحد نهائي هو أن المصدر articles لهذا المحرك يجب أن يكون هو جذر المحرك. عندما يذهب شخص ما إلى مسار الجذر حيث يوصل المحرك، يجب أن يُعرَّض عليه قائمة بالمقالات. يمكن إجراء ذلك إذا أُدرج السطر التالي في الملف config/routes.rb داخل المحرك:

root to: "articles#index"

الآن سيحتاج الأشخاص فقط إلى جذر المحرك لرؤية جميع المقالات، بدلًا من زيارة ‎/articles. هذا يعني أنه بدلًا من http://localhost:+3000/blorgh/articles، ما عليك سوى الانتقال إلى http://localhost:3000/blorgh الآن.

توليد مصدر للتعليقات

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

من جذر التطبيق، شغل مولد النموذج. أخبره بإنشاء نموذج تعليق، مع الجدول ذي الصلة الذي به عمودين: العمود article_id الذي يمثِّل عددًا صحيحًا والعمود text الذي يمثِّل نصًّا.

$ bin/rails generate model Comment article_id:integer text:text

مخرجات تنفيذ هذا الأمر هي:

invoke  active_record
create    db/migrate/[timestamp]_create_blorgh_comments.rb
create    app/models/blorgh/comment.rb
invoke    test_unit
create      test/models/blorgh/comment_test.rb
create      test/fixtures/blorgh/comments.yml

سيولد هذا الاستدعاء فقط ملفات النماذج الضرورية التي تحتاجها، حيث تسمى الملفات تحت المجلد blorgh ويُنشئ صنف نموذج (model class) يسمى Blorgh::Comment. الآن شغل التهجير لإنشاء الجدول blorgh_comments الخاص بنا:

$ bin/rails db:migrate

لإظهار التعليقات على إحدى المقالات، عدّل app/views/blorgh/articles/show.html.erb وأضف هذا السطر قبل الرابط "Edit" (تعديل):

<h3>Comments</h3>
<%= render @article.comments %>

سوف يتطلب هذا السطر أن يكون هناك الارتباط has_many للتعليقات المحددة في النموذج Blorgh::Article، والتي لا توجد الآن. لتحديد واحد، افتح app/models/blorgh/article.rb وأضف السطر التالي إلى النموذج:

has_many :comments

تحويل النموذج إلى:

module Blorgh
  class Article < ApplicationRecord
    has_many :comments
  end
end

ملاحظة: نظرًا لتعريف الارتباط has_many داخل صنف موجودة داخل الوحدة Blorgh، سيعرف ريلز أنك تريد استخدام نموذج Blorgh::Comment لهذه الكائنات، لذا لا داعي لتحديد ذلك باستخدام الخيار ‎:class_name هنا.

بعد ذلك، يجب أن يكون هناك نموذج حتى يمكن إنشاء التعليقات على المقالة. لتوفير هذا، ضع السطر التالي أسفل الاستدعاء render @article.comments في app/views/blorgh/articles/show.html.erb:

<%= render "blorgh/comments/form" %>

بعد ذلك، تحتاج الجزئية (partial) التي سيصيرها هذا السطر إلى أن تكون موجودة. أنشئ مجلدًا جديدًا في app/views/blorgh/comments وأنشئ ملفًا فيه باسم form.html.erb_ الذي يحتوي على المحتوى التالي لإنشاء الجزئية المطلوبة:

<h3>New comment</h3>
<%= form_with(model: [@article, @article.comments.build], local: true) do |form| %>
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
  <%= form.submit %>
<% end %>

عندما يرسل هذا النموذج، فإنه سيحاول تنفيذ طلب POST إلى المسار/articles/:article_id/comments داخل المحرك. هذا المسار غير موجود في الوقت الحالي، ولكن يُنشأ عن طريق تغيير السطر resources :articles داخل config/routes.rb إلى هذه السطور:

resources :articles do
  resources :comments
end

يؤدي هذا إلى إنشاء مسار متداخل للتعليقات، وهو ما يتطلبه النموذج.

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

$ bin/rails g controller comments

هذا سوف يولد الأشياء التالية:

create  app/controllers/blorgh/comments_controller.rb
invoke  erb
 exist    app/views/blorgh/comments
invoke  test_unit
create    test/controllers/blorgh/comments_controller_test.rb
invoke  helper
create    app/helpers/blorgh/comments_helper.rb
invoke  assets
invoke    js
create      app/assets/javascripts/blorgh/comments.js
invoke    css
create      app/assets/stylesheets/blorgh/comments.css

سيقدم النموذج طلب POST إلى ‎/articles/:article_id/comments، والتي تتوافق مع الإجراء create في Blorgh::CommentsController. يجب إنشاء هذا الإجراء، والذي يُنفذ عن طريق وضع السطور التالية داخل تعريف الصنف في app/controllers/blorgh/comments_controller.rb:

def create
  @article = Article.find(params[:article_id])
  @comment = @article.comments.create(comment_params)
  flash[:notice] = "Comment has been created!"
  redirect_to articles_path
end
 
private
  def comment_params
    params.require(:comment).permit(:text)
  end

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

Missing partial blorgh/comments/_comment with {:handlers=>[:erb, :builder],
:formats=>[:html], :locale=>[:en, :en]}. Searched in:   *
"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"   *
"/Users/ryan/Sites/side_projects/blorgh/app/views"

يتعذر على المحرك العثور على الجزء المطلوب لتقديم التعليقات. يبحث ريلز أولًا في مجلد التطبيق test/dummy) app/views) ثم في المجلد app/views للمحرك. عندما لا يعثر عليه، سوف يرمي هذا الخطأ. يعرف المحرك أين يبحث عن blorgh/comments/_comment لأن كائن النموذج الذي يستقبله هو من الصنف Blorgh::Comment.

ستكون هذه الجزئية مسؤولة عن تقديم نص التعليق فقط، في الوقت الحالي. أنشئ ملفًا جديدًا في app/views/blorgh/comments/_comment.html.erb وضع هذا السطر داخله:

<%= comment_counter + 1 %>. <%= comment.text %>

يعطى المتغير المحلي comment_counter بواسطة استدعاء ‎<%= render@article.comments %>‎، والذي سيحدده تلقائيًا ويزيد العداد أثناء تكرار كل تعليق. يُستخدم في هذا المثال لعرض رقم صغير بجوار كل تعليق عند إنشائه.

هذا يكمل وظيفة التعليق لمحرك التدوين. الآن حان الوقت لاستخدامه داخل التطبيق.

استعمال المحرك في التطبيق

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

وصل المحرك

أولًا، يجب تحديد المحرك داخل Gemfile الخاص بالتطبيق. إذا لم يكن هناك تطبيق مفيد لاختبار ذلك، فأنشئ تطبيقًا باستخدام الأمر rails new خارج مجلد المحرك كما يلي:

$ rails new unicorn

عادةً، يحدد المحرك داخل Gemfile من خلال تحديده كالمعتاد كأي جوهرة.

gem 'devise'

ومع ذلك، نظرًا لأنك تقوم بتطوير المحرك blorgh على جهازك المحلي، فستحتاج إلى تحديد الخيار path:‎ في Gemfile الخاص بك:

gem 'blorgh', path: 'engines/blorgh'

ثم شغل bundle لتثبيت الجوهرة.

كما هو موضح سابقًا، بوضع الجوهرة في Gemfile، سيحمل المحرك عند تحميل ريلز. سيتطلب الأمر أولًا lib/blorgh.rb من المحرك، ثم lib/blorgh/engine.rb، وهو الملف الذي يحدد الأجزاء الرئيسية من الوظائف للمحرك.

لجعل وظيفة المحرك قابلة للوصول من داخل التطبيق، يجب تثبيته في الملف config/path.rb لهذا التطبيق:

mount Blorgh::Engine, at: "/blog"

سيوصل هذا السطر المحرك في ‎/blog  في التطبيق. مما يجعل الوصول إليها في http://localhost:3000/blog عند تشغيل التطبيق مع rails server.

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

إعداد المحرك

يحتوي المحرك على عمليات تهجير لجدول blorgh_articles والجدول blorgh_comments والتي يلزم إنشاؤها في قاعدة بيانات التطبيق بحيث يمكن لنماذج المحرك الاستعلام عنها بشكل صحيح. لنسخ هذه التهجيرات إلى التطبيق، شغل الأمر التالي من المجلد test/dummy الخاص بمحرك ريلز:

$ bin/rails blorgh:install:migrations

إذا كان لديك العديد من المحركات التي تحتاج إلى نسخ عمليات التهجير، استخدام railties:install:migrations بدلًا من ذلك:

$ bin/rails railties:install:migrations

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

Copied migration [timestamp_1]_create_blorgh_articles.blorgh.rb from blorgh
Copied migration [timestamp_2]_create_blorgh_comments.blorgh.rb from blorgh

سيكون الطابع الزمني الأول ([timestamp_1]) هو الوقت الحالي، وسيكون الطابع الزمني الثاني ([timestamp_2]) هو الوقت الحالي بالإضافة إلى ثانية. السبب في ذلك هو أن تُشغل عمليات ji[dv المحرك بعد أي عمليات تهجير حالية في التطبيق.

لتشغيل عمليات التهجير هذه في سياق التطبيق، ما عليك سوى تشغيل bin/rails db:migrate. عند الوصول إلى المحرك من خلال http://localhost: 3000/blog، ستكون المقالات فارغة. وذلك لأن الجدول الذي أُنشأ داخل التطبيق يختلف عن الجدول الذي أُنشأ داخل المحرك. امضي قدمًا واستمر بالتعديل على المحرك الذي وصل حديثًا. ستجد أنه هو نفسه عندما كان مجرد محرك.

إذا كنت ترغب في تشغيل عمليات التهجير فقط من محرك واحد، فيمكنك القيام بذلك عن طريق تحديد SCOPE:

bin/rails db:migrate SCOPE=blorgh

قد يكون هذا مفيدًا إذا كنت تريد التراجع عن عمليات ترحيل المحرك قبل إزالتها. لإعادة جميع عمليات الترحيل من المحرك blorgh، يمكنك تشغيل شيفرة مثل:

bin/rails db:migrate SCOPE=blorgh VERSION=0

استخدام صنف وفره التطبيق

استخدام نموذج وفره التطبيق

عندما يُنشأ المحرك، قد ترغب في استخدام أصناف معينة من التطبيق لتوفير روابط بين أجزاء المحرك وأجزاء التطبيق. في حالة المحرك blorgh، فإن إسناد مؤلفين إلى المقالات والتعليقات تجعل الأمر أكثر واقعية.

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

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

rails g model user name:string

يجب تشغيل الأمر bin/rails db: migrate هنا لضمان أن تطبيقنا يحتوي على الجدول users للاستخدام المستقبلي.

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

أولًا، يجب إضافة حقل النص author_name إلى الجزئية app/views/blorgh/articles/_form.html.erb داخل المحرك. يمكن إضافة هذا فوق الحقل title بالشكل التالي:

<div class="field">
  <%= form.label :author_name %><br>
  <%= form.text_field :author_name %>
</div>

بعد ذلك، نحتاج إلى تحديث التابع Blorgh::ArticleController.article_params للسماح بمعامل النموذج الجديد:

def article_params
  params.require(:article).permit(:title, :text, :author_name)
end

يجب أن يحتوي النموذج Blorgh::Article على نفس الشيفرة لتحويل الحقل author_name إلى كائن User فعلي و إقرانه على أنه مؤلف author تلك المقالة قبل حفظ حفظها. كما ستحتاج أيضًا إلى إعداد attr_accessor لهذا الحقل، بحيث تُحدد توابع الضبط (setter) والجلب (getter) الخاصة به.

للقيام بكل هذا، ستحتاج إلى إضافة attr_accessor لـ author_name، إذ يستدعى الارتباط للمؤلف والاستدعاء before_validation إلى app/models/blorgh/article.rb. سيكون الارتباط author ثابتًا (hard-coded) إلى الصنف User في الوقت الحاضر.

attr_accessor :author_name
belongs_to :author, class_name: "User"
 
before_validation :set_author
 
private
  def set_author
    self.author = User.find_or_create_by(name: author_name)
  end

من خلال تمثيل الارتباط author للكائن مع الصنف User، تنشأ وصلةٌ بين المحرك والتطبيق. يجب أن تكون هناك طريقة لربط السجلات في الجدول blorgh_articles بالسجلات في الجدول users. لأن الارتباط يدعى author، يجب أن يكون هناك حقلًا يدعى author_id مضافًا إلى الجدول blorgh_articles.

لإنشاء هذا العمود الجديد، شغل هذا الأمر داخل المحرك:

$ bin/rails g migration add_author_id_to_blorgh_articles author_id:integer

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

يجب تشغيل هذا التهجير على التطبيق. للقيام بذلك، يجب أولًا نسخه باستخدام هذا الأمر:

$ bin/rails blorgh:install:migrations

لاحظ أنه نُسخ تهجير واحد فقط هنا. هذا لأن نُسخ أول تهجيرين خلال المرة الأولى من تشغيل هذا الأمر.

NOTE Migration [timestamp]_create_blorgh_articles.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
NOTE Migration [timestamp]_create_blorgh_comments.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blorgh

شغّل التهجير باستخدام:

$ bin/rails db:migrate

الآن مع وضع كل القطع في مكانها، سيجري ربط مؤلفٍ - مأخوذ من حقلٍ من الجدول users - مع كل مقال ممثل من الجدول blorgh_articles من المحرك.

وأخيرًا، يجب أن يعرض اسم المؤلف على صفحة المقالات. أضف الشيفرة التالية فوق "Title" داخل الملف app/views/blorgh/articles/show.html.erb:

<p>
  <b>Author:</b>
  <%= @article.author.name %>
</p>

استخدام وحدة تحكم وفرها التطبيق

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

تملك محركات ريلز نطاقًا للتشغيل بشكل مستقل عن التطبيق الرئيسي، بحيث يحصل كل محرك على نطاق ApplicationController. يمنع مجال الاسم هذا تعارضات الشيفرة، ولكن غالبًا ما تحتاج وحدات تحكم المحرك إلى الوصول إلى التوابع في ApplicationController للتطبيق الرئيسي. إحدى الطرق السهلة لتوفير هذا الوصول هي تغيير ApplicationController الخاص بالمحرك إلى الوراثة من ApplicationController للتطبيق الرئيسي. بالنسبة لمحرك Blorgh، يمكن القيام بذلك عن طريق تغيير app/controllers/blorgh/application_controller.rb لتبدو كما يلي:

module Blorgh
  class ApplicationController < ::ApplicationController
  end
end

بشكل افتراضي، ترث وحدات تحكم المحرك من Blorgh::ApplicationController. لذا، بعد إجراء هذا التغيير، سيكون بإمكانها الوصول إلى ApplicationController للتطبيق الرئيسي، كما لو كانت جزءًا من التطبيق الرئيسي.

يتطلب هذا التغيير تشغيل المحرك من تطبيق ريلز يحتوي على ApplicationController.

ضبط المحرك

يغطي هذا القسم كيفية جعل الصنف User قابلة للضبط، ويحتوي على نصائح عامة لتهيئة المحرك.

ضبط إعدادات التطبيق

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

لتعريف خيار الضبط هذا، يجب عليك استخدام mattr_accessor داخل الوحدة Blorgh للمحرك. أضف السطر التالي إلى lib/blorgh.rb داخل المحرك:

mattr_accessor :author_class

يعمل هذا التابع مثل أمثاله - attr_accessor و cattr_accessor - ولكنه يوفر تابع ضبط (setter) وجلب (getter) على النموذج بالاسم المحدد. لاستخدامها، يجب الإشارة إليها باستخدام Blorgh.author_class.

الخطوة التالية هي تبديل النموذج Blorgh::Article إلى هذا الضبط الجديد. غيِّر الارتباط belongs_to داخل هذا النموذج (app/models/blorgh/article.rb) إلى ما يلي:

belongs_to :author, class_name: Blorgh.author_class

يجب أيضًا أن يستخدم التابع set_author في النموذج Blorgh::Article هذا الصنف:

self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)

لحفظ الحاجة إلى استدعاء ثابت على نتيجة author_class طوال الوقت، يمكنك بدلًا من ذلك فقط استبدال التابع author_class الضابط داخل الوحدة Blorgh في الملف lib/blorgh.rb لتستدعي دائمًا constantize على القيمة المحفوظة قبل إرجاع النتيجة:

def self.author_class
  @@author_class.constantize
end

سيؤدي هذا بعد ذلك إلى تحويل الشيفرة أعلاه من أجل set_author إلى:

self.author = Blorgh.author_class.find_or_create_by(name: author_name)

مما يؤدي إلى شيء أقصر قليلًا، وذي سلوك مضمَّن. يجب أن يعيد التابع author_class الكائن Class دومًا.

بما أننا غيرنا التابع author_class لإعادة صنف بدلًا من سلسلة نصية، فيجب علينا أيضًا تعديل تعريف الارتباط belongs_to الخاص بنا في النموذج Blorgh::Article:

belongs_to :author, class_name: Blorgh.author_class.to_s

لتعيين خيار الضبط هذا داخل التطبيق، يجب استخدام مُهيئٍ. باستخدام المُهيئ، سيُضبَط الضبط قبل بدء تشغيل التطبيق ويستدعي نماذج المحرك، والتي قد تعتمد على هذا الضبط الموجود حاليًّا.

أنشئ مُهيئًا جديدًا في config/initializers/blorgh.rb داخل التطبيق حيث وُصِلَ المحرك blorgh وضع هذا المحتوى فيه:

Blorgh.author_class = "User"

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

امضي قدمًا وحاول إنشاء مقال جديد. سترى أنه يعمل بالضبط بنفس الطريقة كما كان من قبل، ما عدا أن المحرك الآن يستخدم إعداد الضبط في config/initializers/blorgh.rb لمعرفة ما هو الصنف.

لا توجد الآن اعتمادات صارمة (strict dependencies) على ما هية الصنف، فقط ما يجب أن تكون عليه واجهة برمجة التطبيقات للصنف. يتطلب المحرك ببساطة هذه الصنف لتعريف التابع find_or_create_by الذي يعيد عنصرًا من ذلك الصنف، لربطه بمقال عند ضبطه. هذا الكائن، بالطبع، يجب أن يحتوي على نوع من المعرّف يمكن من خلاله الإشارة إليه.

الضبط العام للمحرك

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

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

محليًا، ضع ببساطة ملفات الإعدادات المحلية في المجلد config/locales، تمامًا كما تفعل في أي تطبيق.

اختبار المحرك

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

يجب معاملة المجلد test مثل بيئة اختبار ريلز النموذجية، مما يسمح باختبارات الوحدة والوظيفة والتكامل.

الاختبارات الوظيفية

هناك مسألة تستحق الأخذ في الحسبان عند كتابة الاختبارات الوظيفية وهي أن الاختبارات ستعمل على تطبيق - تطبيق test/dummy - بدلًا من المحرك الخاص بك. هذا بسبب إعداد بيئة الاختبار؛ يحتاج المحرك إلى تطبيق كمضيف لاختبار وظائفه الرئيسية، وخاصة أجهزة التحكم. هذا يعني أنه إذا كنت ستجعل GET نموذجية إلى وحدة التحكم في اختبار وظيفية وحدة تحكم مثل هذا:

module Blorgh
  class FooControllerTest < ActionDispatch::IntegrationTest
    include Engine.routes.url_helpers
 
    def test_index
      get foos_url
      ...
    end
  end
end

قد لا يعمل بشكل صحيح وذلك لأن التطبيق لا يعرف كيفية توجيه هذه الطلبات إلى المحرك ما لم تخبره بوضوح كيف. للقيام بذلك، يجب عليك تعيين متغير النسخة routes@ إلى مسار توجيه المحرك الذي عُيِّن في شيفرة الإعداد الخاصة بك:

module Blorgh
  class FooControllerTest < ActionDispatch::IntegrationTest
    include Engine.routes.url_helpers
 
    setup do
      @routes = Engine.routes
    end
 
    def test_index
      get foos_url
      ...
    end
  end
end

هذا يخبر التطبيق أنك لا تزال ترغب في تنفيذ طلب GET إلى الإجراء index لوحدة التحكم هذه، ولكنك تريد استخدام مسار المحرك للوصول إلى هناك، بدلًا من مسار التطبيق.

يضمن هذا أيضًا أن يعمل مساعدي عنوان URL الخاصة بالمحرك كما هو متوقع في اختباراتك.

تحسين وظائف المحرك

يشرح هذا القسم كيفية إضافة و/أو تجاوز وظائف نموذج MVC للمحرك في تطبيق ريلز الرئيسي.

استبدال النماذج ووحدات التحكم

يمكن توسيع نموذج المحرك وأصناف وحدة التحكم عن طريق فتحها في تطبيق ريلز الرئيسي (نظرًا لأن أصناف النموذج ووحدة التحكم ليست سوى أصناف روبي ترث وظيفة معينة في ريلز). فتح تصنيف (open classing) صنف المحرك يعيد تعريفه للاستخدام في التطبيق الرئيسي. يُنفَّذ ذلك عادةً باستخدام نمط المزخرف (decorator pattern).

بالنسبة لتعديلات صنف بسيطة، استخدم التابع Class.class_eval. لتعديلات صنف معقدة، ضع في اعتبارك استخدام ActiveSupport::Concern.

ملاحظة حول شيفرة المزخرفات والتحميل

نظرًا لأن هذه المزخرفات (decorators) لا يُشَار إليها من خلال تطبيق ريلز نفسه، فلن يُطبَّق نظام التحميل التلقائي الخاص بريلز لتحميل المزخرفات الخاصة بك. هذا يعني أنك تحتاج إلى طلبها بنفسك.

إليك شيفرة نموذجية توضح كيفية القيام بذلك:

# lib/blorgh/engine.rb
module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
 
    config.to_prepare do
      Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c|
        require_dependency(c)
      end
    end
  end
end

هذا لا ينطبق فقط على المزخرفات (Decorators)، ولكن أي شيء تضيفه في محرك لا يشار إليه بواسطة التطبيق الرئيسي.

تنفيذ نمط المزخرف باستخدام التابع Class.class_eval

إضافة Article.time_since_created:

# MyApp/app/decorators/models/blorgh/article_decorator.rb
 
Blorgh::Article.class_eval do
  def time_since_created
    Time.current - created_at
  end
end
# Blorgh/app/models/article.rb
 
class Article < ApplicationRecord
  has_many :comments
end

استبدال Article.summary:

# MyApp/app/decorators/models/blorgh/article_decorator.rb
 
Blorgh::Article.class_eval do
  def summary
    "#{title} - #{truncate(text)}"
  end
end
# Blorgh/app/models/article.rb
 
class Article < ApplicationRecord
  has_many :comments
  def summary
    "#{title}"
  end
end

تنفيذ نمط المزخرف باستخدام ActiveSupport::Concern

يعد استخدام Class.class_eval مثاليًا لإجراء عمليات ضبط بسيطة، ولكن بالنسبة لتعديلات صنف أكثر تعقيدًا، قد ترغب في استخدام ActiveSupport::Concern. يدير ActiveSupport::Concern ترتيب التحميل للوحدات والأصناف التابعة المترابطة في وقت التشغيل مما يسمح لك بتهيئة شيفرتك بشكل كبير.

إضافة Article.time_since_created واستبدال Article.summary:

# MyApp/app/models/blorgh/article.rb
 
class Blorgh::Article < ApplicationRecord
  include Blorgh::Concerns::Models::Article
 
  def time_since_created
    Time.current - created_at
  end
 
  def summary
    "#{title} - #{truncate(text)}"
  end
end
# Blorgh/app/models/article.rb
 
class Article < ApplicationRecord
  include Blorgh::Concerns::Models::Article
end
# Blorgh/lib/concerns/models/article.rb
 
module Blorgh::Concerns::Models::Article
  extend ActiveSupport::Concern
 
  # بتقييم الشيفرة المضمنة في السياق 'included do' يتسبب
  # بدلًا من تنفيذها في سياق  (article.rb) حيث ضُمِّنَت
  # (blorgh/concerns/models/article) الوحدة
  included do
    attr_accessor :author_name
    belongs_to :author, class_name: "User"
 
    before_validation :set_author
 
    private
      def set_author
        self.author = User.find_or_create_by(name: author_name)
      end
  end
 
  def summary
    "#{title}"
  end
 
  module ClassMethods
    def some_class_method
      'some class method string'
    end
  end
end

استبدال الواجهات

عندما يبحث ريلز عن الواجهة التي ستعرض، سيبحث أولًا في المجلد app/views للتطبيق. إذا لم يتمكن من العثور على الواجهة هناك، فسيتحقق من المجلدات الموجودة في app/views لجميع المحركات التي تحتوي على هذا المجلد.

عندما يُطلب من التطبيق تصيير واجهة Blorgh::ArticlesController للإجراء index، سيبحث أولًا عن المسار app/views/blorgh/articles/index.html.erb ضمن التطبيق. إذا لم تتمكن من العثور عليه، فسيظهر داخل المحرك.

يمكنك تجاوز هذه الواجهة واستبدالها في التطبيق ببساطة عن طريق إنشاء ملف جديد في app/views/blorgh/articles/index.html.erb. بعد ذلك، يمكنك تغيير طريقة عرض هذه الواجهة تمامًا.

جرب هذا الآن عن طريق إنشاء ملف جديد في app/views/blorgh/articles/index.html.erb ووضع هذا المحتوى فيه:

<h1>Articles</h1>
<%= link_to "New Article", new_article_path %>
<% @articles.each do |article| %>
  <h2><%= article.title %></h2>
  <small>By <%= article.author %></small>
  <%= simple_format(article.text) %>
  <hr>
<% end %>

المسارات الموجهة

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

تُحدَّد المسارات الموجهة داخل المحرك في الصنف Engine داخل config/routes.rb، على النحو التالي:

Blorgh::Engine.routes.draw do
  resources :articles
end

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

على سبيل المثال، سيذهب المثال التالي إلى article_path للتطبيق إذا عُرض هذا القالب من التطبيق، أو article_path الخاص بالمحرك إذا عُرض من المحرك:

<%= link_to "Blog articles", articles_path %>

ولجعل هذا المسار دائمًا يستخدم تابع مساعد التوجيه article_path للمحرك، يجب علينا استدعاء التابع على تابع التوجيه الوسيط الذي يشترك في نفس اسم المحرك.

<%= link_to "Blog articles", blorgh.articles_path %>

إذا كنت تريد الرجوع إلى التطبيق داخل المحرك بطريقة مماثلة، فاستخدم المساعد main_app:

<%= link_to "Home", main_app.root_path %>

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

إذا حاول قالب صُيِّر من داخل محرك ما استخدام أحد توابع مساعد التوجيه للتطبيق، فقد ينتج عنه استدعاء تابع غير محدد. إذا واجهتك مشكلة من هذا القبيل، فتأكد من أنك لا تحاول استدعاء تابع توجيه التطبيق بدون البادئة main_app من داخل المحرك.

الأصول

تعمل الأصول داخل المحرك بطريقة مماثلة لتطبيق كامل. نظرًا لأن الصنف Engine يرث من Rails::Engine، سيعرف التطبيق البحث عن الأصول في المحرك app/assets والمجلدات  lib/assets.

مثل جميع المكونات الأخرى للمحرك، يجب أن تكون الأصول ضمن مجال اسم. وهذا يعني أنه إذا كان لديك أصل باسم style.css، فيجب وضعه في app/assets/stylesheets/[engine name]/style.css، بدلًا من app/assets/stylesheets/style.css. إذا لم يكن لدى هذا الأصل مجال اسم، فمن المحتمل أن يكون لدى التطبيق المضيف أصلًا يملك نفس الاسم تمامًا، وفي هذه الحالة يكون لأصل التطبيق الأولوية وسيتجاهل المحرك.

تخيل أن لديك أصلًا موجودًا في app/assets/stylesheets/blorgh/style.css لتضمين الأصل هذا داخل أحد التطبيقات، ما عليك سوى استخدام stylesheet_link_tag والإشارة إلى الأصل كما لو كان داخل المحرك:

<%= stylesheet_link_tag "blorgh/style.css" %>

يمكنك أيضًا تحديد هذه الأصول باعتبارها تبعيات للأصول الأخرى باستخدام Asset Pipeline تتطلب تعابير في الملفات التي تمت معالجتها:

/*
 *= require blorgh/style
*/

ملاحظة: تذكر أنه من أجل استخدام لغات مثل Sass أو CoffeeScript، يجب عليك إضافة المكتبة ذات الصلة إلى gemspec. الخاص بالمحرك.

الأصول المنفصلة والتصريف المسبق

هناك بعض الحالات التي لا يتطلب فيها التطبيق المضيف أصول محركك. على سبيل المثال، لنفترض أنك أنشأت وظيفة مسؤول لا تتوفر إلا لمحركك. في هذه الحالة، لا يحتاج التطبيق المضيف إلى طلب admin.css أو admin.js. يحتاج تخطيط المشرف فقط إلى هذه الأصول. من غير المعقول أن يتضمن التطبيق المضيف "blorgh/admin.css" في صفحات الأنماط الخاصة به. في هذه الحالة، يجب أن تُعرَّف هذه الأصول بشكل واضح من أجل التصريف المسبق (precompilation). هذا يخبر Sprockets لإضافة أصول المحرك الخاص بك عند تشغيل bin/rails assets:precompile.

يمكنك تعريف الأصول للتصريف المسبق في engine.rb:

initializer "blorgh.assets.precompile" do |app|
  app.config.assets.precompile += %w( admin.js admin.css )
end

لمزيد من المعلومات، اقرأ دليل Asset Pipeline.

تبعيات الجوهرة الأخرى

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

لتحديد تبعية يجب تثبيتها مع المحرك أثناء تنفيذ الأمر gem install الاعتيادي، حددها داخل الكتلة Gem::Specification الموجودة داخل الملف gemspec. في المحرك:

s.add_dependency "moo"

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

s.add_development_dependency "moo"

تُثبت كلا النوعين من التبعيات عند تنفيذ الأمر bundle install داخل التطبيق. لن يستخدم تبعيات التطوير للجوهرة إلا عند تشغيل اختبارات المحرك.

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

require 'other_engine/engine'
require 'yet_another_engine/engine'
 
module MyEngine
  class Engine < ::Rails::Engine
  end
end

Active Support وخطافات On Load

Active Support هو مكون ريلز المسؤول عن توفير ملحقات لغة روبي والأدوات المساعدة والأدوات الأخرى.

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

الخطافات On Load هي واجهة برمجة التطبيقات التي تسمح لك بالتحميل في عملية الإقلاع دون انتهاك عقد التحميل مع ريلز. سيؤدي ذلك أيضًا إلى تخفيف تدهور أداء الإقلاع وتجنب التعارضات.

ما هي خطافات on_load؟

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

ActiveRecord::Base.include(MyActiveRecordHelper)

يعني هذا المقتطف أنه عند تحميل هذا الملف، سيُنفَّذ ActiveRecord::Base. تؤدي هذا التنفيذ إلى قيام روبي بالبحث عن تعريف هذا الثابت وسيتطلبه عبر require. يؤدي هذا لتحميل إطار Active Record  بالكامل في الإقلاع.

ActiveSupport.on_load هو آلية يمكن استخدامها لتأجيل تحميل الشيفرة إلى أن تكون ضرورية بالفعل. يمكن تغيير المقتطف أعلاه إلى:

ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper }

يتضمن هذا المقتطف الجديد MyActiveRecordHelper فقط عند تحميل ActiveRecord::Base.

كيف يعمل؟

في إطار ريلز، تُستدعى هذه الخطافات عند تحميل مكتبة معينة. على سبيل المثال، عند تحميل ActionController::Base، يُستدعَى الخطاف ‎:action_controller_base. هذا يعني أن جميع الاستدعاءات ActiveSupport.on_load مع خطافات  action_controller_base: تستدعى في سياق ActionController::Base (وهذا يعني أن self سوف يكون ActionController::Base).

تعديل الشيفرة لاستخدام خطافات on_load

تعديل الشيفرة بشكل عام بسيط. إذا كان لديك سطر من التعليمات البرمجية يشير إلى إطار ريلز مثل ActiveRecord::Base يمكنك تغليف تلك الشيفرة في خطاف on_load.

مثال 1

ActiveRecord::Base.include(MyActiveRecordHelper)

يصبح:

ActiveSupport.on_load(:active_record) { include MyActiveRecordHelper } # self refers to ActiveRecord::Base here, so we can simply #include

مثال 2

ActionController::Base.prepend(MyActionControllerHelper)

يصبح:

ActiveSupport.on_load(:action_controller_base) { prepend MyActionControllerHelper } 
# #prepend لذا يمكننا ببساطة ،ActionController::Base هنا إلى self تشير

مثال 3

ActiveRecord::Base.include_root_in_json = true

يصبح:

ActiveSupport.on_load(:active_record) { self.include_root_in_json = true } 
# ActiveRecord::Base هنا إلى self يشير

الخطافات المتاحة

ستجد في هذا القسم الخطافات التي يمكنك استخدامها في الشيفرة الخاصة بك.

للربط في عملية التهيئة لإحدى الأصناف التالية، استخدم الخطاف المتوفر.

الصنف الخطاف المتاح
ActionCable action_cable
ActionController::API action_controller_api
ActionController::API action_controller
ActionController::Base action_controller_base
ActionController::Base action_controller
ActionController::TestCase action_controller_test_case
ActionDispatch::IntegrationTest action_dispatch_integration_test
ActionDispatch::SystemTestCase action_dispatch_system_test_case
ActionMailer::Base action_mailer
ActionMailer::TestCase action_mailer_test_case
ActionView::Base action_view
ActionView::TestCase action_view_test_case
ActiveJob::Base active_job
ActiveJob::TestCase active_job_test_case
ActiveRecord::Base active_record
ActiveSupport::TestCase active_support_test_case
i18n i18n

ضبط الخطافات

إليك خطافات الضبط المتاحة، فهي لا تُعلِّق (hook) في أي إطار معين، بل تعمل في سياق التطبيق بأكمله.

الخطاف حالة الاستخدام
before_configuration أول كتلة قابلة للضبط يراد تنفيذها. تُستدعى قبل أي تشغيل أية مهيئات.
before_initialize ثاني كتلة قابلة للضبط يراد تنفيذها. تُستدعى قبل تهيئة الأطر.
before_eager_load ثالث كتلة ثابلة للضبط يراد تنفيذها. لا يعمل إذا كان config.eager_load مضبوطًا إلى القيمة false.
after_initialize آخر كتلة قابلة للضبط يراد تشغيلها. تُستدعى بعد تهيئة الأطر.

إليك المثال التالي:

Config.before_configuration { puts 'I am called before any initializers' }

مصادر