الشروع في العمل مع المحركات في ريلز
ستتعرف في هذا الدليل على المحركات (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' }