البدء مع ريلز

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

يغطي هذا الدليل بدء وتشغيل ريلز. بعد قراءة هذا الدليل، ستتعلم:

  • كيف تثبّت ريلز وتُنشئ تطبيق ريلز جديد وتربط تطبيق ريلز بقاعدة بيانات.
  • التخطيط العام لتطبيق ريلز.
  • المبادئ الأساسية للنمط MVC (النموذج [Model]، والواجهة [View]، ووحدة التحكم [Controller]) والتصميم RESTful.
  • كيف تولد سريعًا أجزاء البداية لتطبيق ريلز.

افتراضات الدليل

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

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

تنبه أن بعض المصادر بالرغم من أنها ممتازة إلا أنها تتناول إصدارات قديمة من لغة روبي مثل الإصدار 1.6 وغالبًا الإصدار 1.8، وبالتالي لن تجد بها قواعد الصياغة التي ستجدها كلما تقدمت بإطار العمل ريلز.

ما هو إطار العمل ريلز؟

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

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

تتضمن فلسفة ريلز مبدأين توجيهيين أساسيين:

  • لا تكرر نفسك: هو مبدأ تطوير للبرمجيات ينص على أن "كل جزء من المعرفة يجب أن يكون له تمثيل واحد، لا لبس فيه وموثَّق في النظام." من خلال عدم كتابة نفس المعلومات مرارًا وتكرارًا، لتكون شيفراتنا أكثر قابلية للإصلاح  وللاستزادة، وأقل أخطاءً.
  • العرف فوق الضبط: لدى ريلز آراء حول أفضل طريقة للقيام بأشياء كثيرة في تطبيق ويب تجعل مجموعة الأعراف (conventions) تلك في أوضاع افتراضية، بدلًا من أن تتطلب منك تحديد التفاصيل من خلال ملفات إعدادات لا نهاية لها.

إنشاء مشروع جديد بإطار العمل ريلز

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

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

تنبيه: تستخدم الأمثلة أدناه الرمز $ لتمثيل محث الطرفية في أنظمة التشغيل الشبيهة بيونكس، على الرغم من أنه قد خُصّص ليظهر بشكل مختلف. إذا كنت تستخدم نظام التشغيل ويندوز، فسيظهر المحثّ الخاص بك بشكل يشبه c:\source_code>‎.

تثبيت ريلز

قبل تثبيت ريلز، يجب عليك التحقق من أن النظام لديك به المتطلبات الأساسية والمناسبة مثبتة. وذلك يشمل روبي و SQLite3.

افتح محثّ سطر الأوامر. في نظام التشغيل ماك، ثم افتح Terminal.app؛ في نظام التشغيل ويندووز، اختر "بحث" من قائمة البدء واكتب "cmd.exe". أي أمر مسبوق بعلامة $ سيشغل في سطر الأوامر.

تحقق من تثبيت الإصدار الحالي من روبي:

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]

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

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

إن كنت تستعمل نظام التشغيل ويندوز، فعليك تثبيت Ruby Installer Development Kit.

ستحتاج أيضًا إلى تثبيت قاعدة البيانات SQLite3. تتضمّن العديد من أنظمة التشغيل الشبيهة بيونكس قاعدة البيانات SQLite3 بإصدار مقبول. في نظام ويندوز، إذا ثبت ريلز عبر مُثبِّت ريلز، فستُثبّت قاعدة البيانات SQLite ضمنيًّا. يمكن للآخرين العثور على إرشادات التثبيت على موقع SQLite3. تحقق من أنها مثبتة بشكل صحيح وفي PATH الخاص بك:

$ sqlite3 --version

يجب أن يظهر الإصدار الحالي المثبَّت لديك. لتثبيت ريلز، استخدم الأمر gem install المقدم بواسطة RubyGems:

$ gem install rails

للتحقق من أن كل شيء لديك مثبت بطريقة صحيحة، عليك باستخدام الأمر التالي:

$ rails --version

إذا كان الإصدار الظاهر شيئًا شبيهًا بالعبارة "Rails 5.2.1"، فأنت الآن مُستعدٌ لإكمال الرحلة.

إنشاء تطبيق المدونة

يأتي ريلز مع عدد من الأوامر النصية التي تسمى مولدات صُمّمت لجعل أمر التطوير أسهل لك من خلال إنشاء كل ما هو ضروري لبدء العمل في مهمة معينة. أحد هذه المولدات هو مولد تطبيق جديد (new application generator)، والذي سيوفر لك أساس تطبيق ريلز جديد حتى لا تضطر إلى كتابته بنفسك.

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

$ rails new blog

هذا الأمر سينشئ تطبيق ريلز جديد باسم Blog في المجلد blog ويثبت اعتماديات gem التي ذُكرت بالفعل في Gemfile باستخدام bundle install.

ملاحظة: إذا كنت تستخدم نظام ويندووز فرعي على نظام لينكس، هذا يعني أن هناك بعض القيود بخصوص تنبيهات نظام الملفات وبالتالي يجب تعطيل الجوهرة spring و listen؛ يمكنك فعل ذلك بتنفيذ الأمر rails new blog - skip-spring --skip-listen بدلًا من الأمر السابق.

تنبيه: يمكنك رؤية جميع خيارات سطر الأوامر التي يمكن استعمالها مع منشئ تطبيق ريلز وذلك عبر الأمر rails new -h.

بعد إنشاء التطبيق blog، انتقل إلى مجلده:

$ cd blog

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

الملف/المجلد الغاية منه
app/‎ يحتوي على وحدات التحكم والنماذج والواجهات والمساعدين ومرسلي رسائل البريد الإلكتروني والقنوات والوظائف والأصول الخاصة بتطبيقك. ستركز على هذا المجلد فيما تبقى من هذا الدليل.
bin/‎ يحتوي على سكريبتات ريلز التي تبدأ تطبيقك، كما يحتوي على نصوص تستخدمها لإعداد وتحديث واستخدام وتشغيل تطبيقك.
config/‎ ضبط مسارات التوجيه وقواعد البيانات الخاصة بتطبيقك وأكثر من ذلك. هناك تفاصيل أكثر تجدها في دليل ضبط تطبيقات ريلز.
config.ru ضبط Rack للخوادم المعتمدة على Rack المُستخدمة لبدء تطبيقك. لمعلومات أكثر عن Rack، الق نظرة على موقع Rack.
db/‎ يحتوي على المُخطط الحالي لقاعدة البيانات، بالإضافة إلى تهجيرات البيانات.
Gemfile

Gemfile.lock

تسمح لك تلك الملفات أن تعين أي اعتمادات الجوهرة (gem) يحتاجها لتطبيقك. تُستخدم تلك الملفات بواسطة Bundler gem. لمعلومات أكثر عن Bundler، الق نظرة على موقع Bundler.
lib/‎ الوحدات الممتدة لتطبيقك.
log/‎ ملفات سجل التطبيق.
package.json يسمح لك هذا الملف أن تعين أية اعتمادات npm يحتاجها لتطبيقك. يُستخدم هذا الملف بواسطة Yarn. لمعلومات أكثر عن Yarn، الق نظرة على موقع Yarn.
public/‎ المجلد الوحيد الذي يُرَى كما هو. يحتوي على ملفات ثابتة وأصول مُجمعة.
Rakefile هذا الملف يُحدِّد موضع المهام ويُحملها والتي من الممكن تشغيلها من سطر الأوامر. تعريفات المهام (task definitions) تُعرَّف من خلال مكونات ريلز. بدلًا من تغيير Rakefile، عليك بإضافة مهامك عن طريق إضافة ملفات إلى المجلد lib/tasks الخاص بتطبيقك.
README.md هذا دليل توجيهي مُختصر لتطبيقك. عليك بتعديل الملف لتخُبر الآخرين بما يقوم به تطبيقك وكيفية ضبطه وهكذا.
test/‎ اختبارات الوحدات والتجهيزات وأدوات الاختبار الأخرى. هذا مُغطى في دليل اختبار تطبيقات ريلز.
tmp/‎ ملفات مؤقتة (مثل ملفات الذاكرة المؤقتة ومعرف العملية [pid] ...إلخ).
vendor/‎ مكان لجميع أكواد الطرف الثالث. في تطبيق ريلز مثالي يشمل هذا الجواهر (gems) التي نُقلت لملف vendor.
gitignore يخبر هذا الملف git بأي الملفات (أو الأنماط) التي يجب أن يُهملها. لمزيد من المعلومات عن إهمال الملفات، الق نظرة على هذه الصفحة.
ruby-version يشمل هذا الملف إصدار روبي الافتراضي.

مرحبًا، ريلز!

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

بدء خادم الويب

لديك بالفعل تطبيق ريلز فعال. لرؤيته، تحتاج إلى بدء خادم ويب على جهاز التطوير الخاص بك. يمكنك ذلك عن طريق تنفيذ الأمر التالي في المجلد blog:

$ bin/rails server

تنبيه: إن كنت تستخدم نظام ويندوز، فيجب عليك تمرير السكربتات ضمن المجلد bin مباشرةً إلى مُفسِر روبي مثل ruby bin\rails server.

تنبيه: يتطلب تفسير (Compiling) ضغط أصول CoffeScript و JavaScript أن تكون لديك بيئة تشغيل JavaScript متاحة على نظامك؛ إذا غابت بيئة التشغيل، سترى الخطأ execjs أثناء تجميع الأصول. عادةً ما يحتوي نظام التشغيل ماك ونظام ويندوز على بيئة تشغيل JaveScript مثبتة. يضيف ريلز الجوهرة mini_racer إلى Gemfile الذي أُنشئ في سطر تعليق من أجل التطبيقات الجديدة ويمكنك إلغاء التعليق إذا كنت بحاجة إلى ذلك. إن therubyrhino هي بيئة تشغيل الموصى بها لمستخدمي JRuby وتضاف بشكل افتراضي إلى Gemfile في التطبيقات التي أُنشئت تحت JRuby. يمكنك التحقق من جميع بيئات التشغيل المدعومة في ExecJS.

سيؤدي ذلك إلى إطلاق Puma، وهو خادم ويب يُوزّع باستخدام ريلز بشكل افتراضي. للاطلاع على تطبيقك قيد التنفيذ، افتح نافذة المتصفح وانتقل إلى العنوان http://localhost:3000. يجب أن تشاهد صفحة معلومات ريلز الافتراضية:

صفحة ريلز الترحيبية
صفحة ريلز الترحيبية

تنبيه: لإيقاف خادم الويب، اضغط على Ctrl + C في نافذة الطرفية حيث يُشغّل الخادم. للتحقق من توقف الخادم، يجب أن ترى مؤشر محثّ الأوامر مرة أخرى. بالنسبة لمعظم الأنظمة الشبيهة بيونكس، بما في ذلك نظام التشغيل ماك، سيكون ظهور الرمز $ كافيًا للدالالة على توقفه. في وضع التطوير (development mode)، لا يتطلب ريلز عادةً إعادة تشغيل الخادم؛ سيسري مفعول التغييرات التي تجريها على الملفات تلقائيًا في الخادم.

إن الصفحة الترحيبية بمثابة كاشف الدخان لتطبيق ريلز جديد، فهي تؤكد أن لديك برنامج مُهيأ جيدًا لخدمة صفحة.

كيف تجعل ريلز يقول "مرحبًا"

لكي تجعل إطار العمل ريلز يقول "مرحبًا"، فعليك بإنشاء وحدة تحكّم (controller) وواجهة (view) على الأقل.

الهدف من وحدة التحكّم استقبال الطلبات الخاصة بالتطبيق. قسم التوجيه (Routing) سيحدد أي الطلبات ستتجه لأي وحدة تحكّم، فغالبًا لكل وحدة تحكّم أكثر من مسار، والمسارات المختلفة يُعمل عليها بإجراءات مختلفة. الهدف من كل إجراء جمع المعلومات لتزويد الواجهة بها.

الهدف من الواجهة عرض المعلومات في صيغة يمكن للإنسان قراءتها. من الفروق المهمة جعل وحدة التحكّم، وليس الواجهة، المسؤولة عن جمع المعلومات. فالواجهة مخصصة فقط لعرض المعلومات. قوالب الواجهة تُكتب بلغة تُسمى eRuby (اختصارًا لكلمة Embedded Ruby) والتي تُعالج بواسطة الدورة الخاصة بالطلبات في ريلز، قبل أن تُرسل للمُستخدم.

لإنشاء وحدة تحكّم جديدة، يجب عليك تشغيل مولد "وحدة التحكّم" (Controller generator) وتخبره بأنك تُريد وحدة تحكّم تُسمى "Welcome" مع إجراء (action) يسمى "index". فقط عليك باتباع التالي:

$ bin/rails generate controller Welcome index

سينشئ ريلز لك العديد من الملفات بالإضافة إلى مسار توجيه.

create  app/controllers/welcome_controller.rb
 route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  test_unit
create    test/controllers/welcome_controller_test.rb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke    test_unit
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.coffee
invoke    scss
create      app/assets/stylesheets/welcome.scss

أهم تلك الملفات وحدة التحكم بالطبع، الموضوعة في app/controllers/welcome_controller.rb والواجهة الموضوعة في app/views/welcome/index.html.erb. افتح الملف app/views/welcome/index.html.erb في محرر النص الخاص بك. وأزل جميع الأكواد الموجودة في الملف، واستبدلها بذلك السطر المفرد من الأكواد:

<h1>مرحبًا، ريلز!</h1>

إعداد الصفحة الرئيسية للتطبيق

بعد أن أنشأنا وحدة التحكم والواجهة، علينا أن نخبر ريلز متى نُريد أن تُظهِر العنوان "مرحبًأ، ريلز!‎". في حالتنا نُريد أن يظهر العنوان السابق عند الانتقال إلى أصل عنوان موقعنا، الذي هو http://localhost:3000 بدلًا من ظهور الصفحة الترحيبية الافتراضية لريلز.

الخطوة التالية هي أنَّه يجب عليك أن تُخبر ريلز أين الموقع الحقيقي لصفحتك الرئيسية. افتح ملف config/routes.rb في مُحررك:

Rails.application.routes.draw do
  get 'welcome/index'
 
  # المتاح ضمن هذا الملف DSL لمزيد من المعلومات حول
  # https://wiki.hsoub.com/Rails/routing اطلع على الصفحة
end

هذا هو مسار تطبيقك الذي يحمل المدخلات بلغة محدَّدة النطاق DSL (اختصار للعبارة domain-specific language) والتي تخبر ريلز كيف تصل الطلبات القادمة بوحدات التحكم والأوامر. حرّر هذا الملف وأضف السطر 'root 'welcome#index. الأمر سيبدو كالتالي:

Rails.application.routes.draw do
  get 'welcome/index'
 
  root 'welcome#index'
end

يخبر السطر 'root 'welcome#index ريلز بتعيين طلبات جذر التطبيق إلى وحدة التحكم Welcome للإجراء index وتخبر الشيفرة 'get 'welcome/index ريلز بتعيين الطلبات http://localhost:3000/welcome/index إلى وحدة التحكم Welcome للإجراء index. أُنشيء هذا مُسبقًا عندما شغلت مولد وحدة التحكم (لتنفيذ الأمر bin/rails generate controller Welcome index).

شغل خادم الويب مرة أخرى إذا أوقفته لتوليد وحدة التحكم (عبر الأمر bin/rails

server) وانتقل إلى العنوان http://localhost:3000  في مُتصفحك. سترى الرسالة "مرحبًا، ريلز!" التي كتبتها في الملف app/views/welcome/index.html.erb، ما يُشير إلى أنَّ المسار الجديد سيذهب حقًا لوحدة التحكم welcome التي تخص الإجراء index وسيصيِّر الواجهة بشكل صحيح.

تنبيه: لمزيد من المعلومات عن تحديد المسار، اطلع على دليل التوجيه من الخارج والداخل.

المواصلة والتشغيل

الآن وبعد أن رأيت كيف تنشئ وحدة تحكم وإجراء وواجهة، دعنا ننشئ شيئًا أكثر تعقيدًا.

في التطبيق Blog، سننشئ الأن موردًا جديدًا. المورد هو مصطلح يُستخدم لجمع الأشياء المتشابهة مثل المقالات والأشخاص والحيوانات. يُمكنك إنشاء وقراءة وتحديث وتدمير عناصر المورد وتلك العمليات تُسمى عمليات CRUD (اختصار للكلمات create، و read، و update، و destroy).

يقدم ريلز التابع resources الذي يمكن استخدامه للتصريح عن مورد REST قياسي. ستحتاج إلى إضافة مورد المقال (article resource) إلى config/routes.rb وبذلك سيبدو الملف بالشكل التالي:

Rails.application.routes.draw do
  get 'welcome/index'
 
  resources :articles
 
  root 'welcome#index'
end

إذا شغلت الأمر bin/rails routes، سترى أنه عرف مسارات توجيه لجميع إجراءات RESTful القياسيّة. سنتطرق لاحقا إلى معنى العمود البادئ (والأعمدة الأخرى) التي ستظهر لك، ولكن الآن لاحظ أن ريلز تستدل على النموذج الفردي article وتستخدم هذا التميز (distinction) بطريقة ذات معنى.

 $ bin/rails routes
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
     articles GET    /articles(.:format)          articles#index
              POST   /articles(.:format)          articles#create
  new_article GET    /articles/new(.:format)      articles#new
 edit_article GET    /articles/:id/edit(.:format) articles#edit
      article GET    /articles/:id(.:format)      articles#show
              PATCH  /articles/:id(.:format)      articles#update
              PUT    /articles/:id(.:format)      articles#update
              DELETE /articles/:id(.:format)      articles#destroy
         root GET    /                            welcome#index

في القسم التالي، ستُضيف إمكانية إنشاء مقالات جديدة في تطبيقك وستتمكن من عرضهم. هذا ما يشير له الحرف "C" (إنشاء) و"R" (قراءة) من CRUD. سيبدو شكل النموذج الذي جرى تنفيذه وفقًا لما سبق بالشكل التالي:

شكل نموذج إضافة المقالات الجديدة الذي أُنشِئ والذي يراد عرضه في تطبيق المدونة.
شكل نموذج إضافة المقالات الجديدة الذي أُنشِئ والذي يراد عرضه في تطبيق المدونة.

إرساء الأساسيات

بدايًة، تحتاج إلى مكان داخل التطبيق لتنشئ مقالًا جديدًا. سيكون العنوان articles/new/ مكانًا جيدًا لذلك. مع مسار التوجيه المُعرَّف مسبقًا، يُمكن إنشاء الطلبات للعنوان articles/new/ داخل التطبيق. انتقل إلى العنوان http://localhost:3000/articles/new وسترى خطأ بالمسار:

خطأ التوجيه الذي سيظهر عند الانتقال إلى العنوان ‎/articles/new في تطبيق المدونة دون وجود وحدة تحكم تخدمه.
خطأ التوجيه الذي سيظهر عند طلب العنوان ‎/articles/new في تطبيق المدونة دون وجود وحدة تحكم تخدمه.

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

$ bin/rails generate controller Articles

إذا فتحت ملف وحدة التحكم app/controllers/articles_controller.rb المولد حديثًا، سترى وحدة تحكم فارغةً تمامًا:

class ArticlesController < ApplicationController
end

وحدة التحكم هي ببساطة صنف يُعرَّف ليرث من ApplicationController. بداخل ذلك الصنف ستُعرف التوابع التي ستُصبح الإجراءات لوحدة التحكم. تلك الإجراءات ستقوم بعمليات CRUD على المقالات بداخل نظامك.

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

إذا حدثت http://localhost:3000/articles/new الآن، ستجد خطأ جديدًا:

ظهور خطأ جديد بعد توليد وحدة التحكم ArticlesController وتحديث الصفحة ‎/articles/new.
ظهور خطأ جديد بعد توليد وحدة التحكم ArticlesController وتحديث الصفحة ‎/articles/new.

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

لتعريف الإجراءات يدويًا، كل ما عليك هو تعريف تابع جديد داخل وحدة التحكم. افتح الملف app/controllers/articles_controller.rb بداخل الصنف ArticlesController، ثمَّ عرف التابع الجديد. ستبدو وحدة التحكم الخاصة بك بالشكل التالي:

class ArticlesController < ApplicationController
  def new
  end
end

بتعريف التابع الجديد في ArticlesController، إذا حدثت الصفحة http://localhost:3000/articles/new ستجد خطأ أخر:

الخطأ الجديد الذي سيظهر بعد تعريف التابع new الجديد في الصنف ArticlesController وتحديث الصفحة ‎/articles/new.
الخطأ الجديد الذي سيظهر بعد تعريف التابع new الجديد في الصنف ArticlesController وتحديث الصفحة ‎/articles/new.

لقد تلقيت هذا الخطأ الآن نظرًا لأن ريلز يتوقع أن تكون الإجراءات لديها واجهات مرتبطة بها لعرض معلوماتها. مع عدم وجود طريقة عرض، سيعيد ريلز استثناء.

لنلقي نظرة على رسالة الخطأ الكاملة مرة أخرى:

ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.

هذا نص طويل جدًّا! دعنا نذهب بسرعة ونفهم ما يعنيه كل جزء منه.

يحدد الجزء الأول القالب المفقود. في هذه الحالة، القالب هو articles/new. سيبحث ريلز  أولًا عن هذا النموذج. إذا لم يعثر عليه، فسيحاول تحميل قالب يسمى application/new. يبحث ريلز عن قالب هنا لأن ArticlesController  محفوظ في ApplicationController.

يحتوي الجزء التالي من الرسالة على request.formats الذي يحدد تنسيق القالب الذي سيُعرض كاستجابة. تم تعيينه على text/html لكوننا طلبنا هذه الصفحة عبر المتصفح، لذلك يبحث ريلز عن قالب HTML. يحدِّد request.variant أي نوع من الأجهزة المادية سيخدم من خلال الاستجابة ويساعد ريلز لتحديد القالب الذي سيستخدم في الاستجابة. request.variant فارغ لكونك لم تقدم أي معلومات.

سيوجد أبسط قالب للعمل في هذه الحالة في app/views/articles/new.html.erb. امتداد اسم الملف هذا مهم: الامتداد الأول هو تنسيق القالب، والامتداد الثاني هو المعالج الذي سيستخدم لإصدار القالب. يحاول ريلز البحث عن قالب يسمى articles/new داخل app/views للتطبيق. تنسيق هذا القالب قد يكون html فقط والمعالج الافتراضي لـ HTML هو erb. يستخدم ريلز معالجات أخرى للتنسيقات الأخرى. يستخدم المعالج builder لبناء نماذج XML ويستخدم المعالج coffee اللغة CoffeeScript لبناء قوالب JavaScript. بما أنك تريد إنشاء نموذج HTML جديد، فستستخدم لغة ERB المصممة لتضمين روبي في HTML.

لذلك يجب أن يسمى الملف بالاسم articles/new.html.erb ويجب أن يكون موجودًا داخل المجلد app/views للتطبيق.

اذهب الآن وأنشئ ملفًّا جديدًا في app/views/articles/new.html.erb واكتب هذا المحتوى فيه:

<h1>New Article</h1>

عند تحديث http://localhost:3000/articles/new سترى أنَّ الصفحة لها عنوان. المسار ووحدة التحكم والإجراء والواجهة يعملون الآن بانسجام! حان الوقت لإنشاء نموذج لمقال جديد.

النموذج الأولى

لإنشاء نموذج داخل ذلك القالب، ستستخدم منشئ النماذج (form builder). منشئ النماذج الأساسيّ لريلز يُقدَّم من خلال التابع المساعد form_with. لاستخدام ذلك التابع، أضف الشيفرة التالية إلى الملف app/views/articles/new.html.erb:

<%= form_with scope: :article, local: true do |form| %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
 
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
 
  <p>
    <%= form.submit %>
  </p>
<% end %>

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

عند استدعاء التابع المساعد form_with، يمرَّر له نطاق تعريف النموذج الذي يكون هو الرمز article: في هذه الحالة. هذا يخبر التابع المساعد form_with ما هو غرض هذا النموذج. داخل بنية التابع، يُستخدم الكائن FormBuilder - والذي يمثل عبر form - لبناء تسميتين (label) وحقلين نصيين (text field)؛ واحد للعنوان وآخر لنص المقال. وأخيرًا، طلب الإرسال (submit) للكائن form سيُنشيء زر الإرسال للإستمارة.

هناك مشكلة واحدة مع هذا النموذج رغم ذلك. إذا فحصت HTML المُولد، عن طريق عرض مصدر الصفحة، سترى أن الخاصية action للنموذج يُشير إلى articles/new/. هذه مشكلة لأن هذا المسار ينتقل إلى الصفحة التي أنت عليها في الوقت الحالي، ويجب استخدام هذا المسار فقط لعرض النموذج لمقال جديد.

يحتاج النموذج إلى استخدام عنوان URL مختلف للانتقال إلى مكان آخر. يمكن القيام بذلك ببساطة مع الخيار url: الخاص بالتابع المساعد form_with. عادةً في ريلز، يسمى عادةً الإجراء المستخدم في عمليات إنشاء نموذج جديدة بالاسم "create"، وبالتالي يجب أن يوجَّه النموذج إلى هذا الإجراء.

عدّل السطر form_with داخل app/views/articles/new.html.erb ليبدو هكذا:

<%= form_with scope: :article, url: articles_path, local: true do |form| %>

في هذا المثال، المُساعد articles_path يُمرَّر إلى الخيار url:. لنرى ما سيفعله ريلز عند ذلك، نعود إلى مخرجات الأمر bin/rails routes:

$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
     articles GET    /articles(.:format)          articles#index
              POST   /articles(.:format)          articles#create
  new_article GET    /articles/new(.:format)      articles#new
 edit_article GET    /articles/:id/edit(.:format) articles#edit
      article GET    /articles/:id(.:format)      articles#show
              PATCH  /articles/:id(.:format)      articles#update
              PUT    /articles/:id(.:format)      articles#update
              DELETE /articles/:id(.:format)      articles#destroy
         root GET    /                            welcome#index

سيُخبر المساعد articles_path الإطار ريلز أن يوجه النموذج إلى نمط العنوان URL المُرتبط بالبادئة articles؛ وسيٌرسل المقال افتراضيًّا طلب post إلى ذلك المسار الموجه. هذا مُرتبط بالإجراء create الخاص بوحدة التحكم الحالية، التي هي ArticlesController.

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

رسالة الخطأ التي ستظهر عند الضغط على زر الإرسال في النموذج.
رسالة الخطأ التي ستظهر عند الضغط على زر الإرسال في النموذج.

يجب عليك الآن إنشاء الإجراء create الخاص بوحدة التحكم ArticlesController لكي يعمل زر الإرسال.

ملاحظة: افتراضيًّا، يرسل التابع المساعد form_with النماذج باستخدام Ajax، وبالتالي يتخطى عمليات إعادة التوجيه للصفحة الكاملة. لجعل هذا الدليل أسهل في الاستخدام، عطلنا هذا السلوك الافتراضي الآن باستخدام local: true.

إنشاء المقالات

للتخلص من رسالة الخطأ الخاصة بإجراء غير معروف "Unknown action"، يُمكنك تعريف الإجراء create داخل الصنف ArticlesController في app/controllers/articles_controller.rb، أسفل الإجراء new كالتالي:

class ArticlesController < ApplicationController
  def new
  end
 
  def create
  end
end

إذا أعدت إرسال النموذج الآن، فقد لا ترى أي تغيير بالصفحة. لا تقلق! يرجع ذلك إلى أن ريلز بشكل افتراضي تستجيب لإجراء ما بالخطأ "204 No Content" إذا لم نحدِّد كيف ستكون الاستجابة. لقد أضفنا للتو الإجراء create، لكن لم نحدِّد أي شيء عن الاستجابة. في هذه الحالة، الإجراء create سيحفظ المقال الجديد في قاعدة البيانات. عند إرسال النموذج، تُرسل حقوله لريلز كمعاملات. يُشار إلى هذه المعاملات داخل إجراءات وحدة التحكم، لتأدية مهمة محددة. لترى كيف تبدو هذه المعاملات، غير الإجراء create كالتالي:

def create
  render plain: params[:article].inspect
end

يأخذ التابع render جدول hash بسيط جدًا بمفتاح يساوي إلى plain: وقيمة تساوي إلى params [ :article].inspect. التابع params هو الكائن الممثل للمعاملات (التي هي الحقول في حالتنا هذه) الآتية من النموذج. يُرجِع التابع params الكائن ActionController: :Parameters، والذي يسمح لك بالوصول إلى مفاتيح الجدول hash باستخدام إمَّا السلاسل النصية أو الرموز. في هذه الحالة، المعاملات ذات الأهمية فقط هي المعاملات الآيتة من النموذج فقط. تنبيه: تأكد من فهمك للتابع params بشكل جيد، لأنك ستستخدمه كثيرًا خلال مسيرتك في روبي. دعنا نلقي نظرة على عنوان URL عشوائي مثل العنوان التالي:

http://www.example.com/?username=dhh&email=dhh@email.co

في هذا العنوان، تكون قيمة [params[:username هي "dhh" وقيمة [params[:email هي "dhh@emial.com". إذا أعدت إرسال النموذج مرة أخرى، سيظهر لديك شيئًا شبيهًا بما يلي:

<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>

هذا الإجراء يُظهر المعاملات للمقال القادم من النموذج، ولكن هذا ليس مفيد حقًا، إذ جربنا ذلك لمعرفة ما الذي يجري خلف الستار. صحيح، يُمكنك الآن رؤية المعاملات ولكن لا يمكن القيام بشيء محدد.

إنشاء النمط Article

تستخدم الأنماط (Models، انتبه عزيزي القارئ هنا إلى أنَّنا استعمال الترجمة "أنماط" عوضًا عن "نماذج" لعدم الخلط بينها وبين Forms) في ريلز اسمًا مفردًا (singular name) بينما تستخدم جداول قاعدة البيانات المقابلة لها اسم جمع (plural name). يوفر ريلز مولدًا لإنشاء الأنماط، إذ يميل معظم المطورون باستخدام ريلز إلى استخدامه عند إنشاء أنماط جديدة. لإنشاء نمط جديد، نفذ الأمر التالي في الطرفية:

$ bin/rails generate model Article title:string text:text

بهذا الأمر، نخبر ريلز أنَّنا نريد نمطًا جديدًا باسم Article, مع خاصية باسم title من نوع السلاسل النصية (string) وخاصية باسم text من النوع النصي. تُضاف هاتان الخاصيتان تلقائيًا إلى الجدول articles في قاعدة البيانات وتُعينان إلى النمط Article.

يستجيب ريلز بإنشاء مجموعة من الملفات. إلى الآن، نهتم فقط بالملف app/models/article.rb وdb/migrate/20140120191729_create_articles.rb (ربما يكون الاسم لديك مختلف قليلًا). الأخير مسئول عن إنشاء بنية قواعد البيانات، والتي سننظر في أمرها فيما بعد.

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

القيام بتهجير

كما رأينا، فإن الأمر bin/rails generate model أنشئ ملف تهجير لقاعدة البيانات (database migration) داخل المجلد db/migrate. التهجيرات هي من أصناف روبي المصممة لتسهيل إنشاء وتعديل جداول قاعدة البيانات. يستخدم ريلز أوامر rake للقيام بالتهجيرات، ومن الممكن إلغاء تهجير بعد تطبيقه على قاعدة البيانات. أسماء ملفات التهجير تشمل  بصمة الوقت (timestamp) لضمان تنفيذها بالترتيب التي أنشأت به.

إذا نظرت في الملف db/migrate/YYYYMMDDHHMMSS_create_articles.rb (تذكر أن الملف الخاص بك سيكون اسمه مختلف قليلًا)، هذا ما ستجده:

class CreateArticles < ActiveRecord::Migration[5.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text
 
      t.timestamps
    end
  end
end

سينشئ التهجير بالأعلى تابعًا يُسمى change والذي سيُستدعَى عند القيام بهذا التهجير. الإجراء المُعرف في هذا التابع يمكن الرجوع عنه أيضًا، ما يعني أن ريلز تعرف كيف ترجع عن أي تغيير أجري عبر هذا التهجير، إن أردت الرجوع عنه فيما بعد. عند القيام بهذا التهجير، سينشئ جدولًا باسم articles بعمود سلسلة نصية (string) واحد وعمود نصي (text). وسينشئ أيضًا حقلي بصمتي وقت (timestamp) مما يسمح لريلز بتتبع وقت إنشاء المقال وأوقات إجراء التحديث.

تنبيه: لمعلومات أكثر عن التهجيرات، ألقِ نظرة على دليل تهجيرات Active Record.

في تلك الأثناء، يمكن استعمال الأمر bin/rails للقيام بالتهجير:

$ bin/rails db:migrate

سينفذ ريلز أمر التهجير هذا ويخبرك بإنشاء الجدول Articles.

==  CreateArticles: migrating ==================================================
-- create_table(:articles)
   -> 0.0019s
==  CreateArticles: migrated (0.0020s) =========================================

ملاحظة: لأنك افتراضيًا تعمل في بيئة تطوير، سيُطبق هذا الأمر في قاعدة البيانات المُعرفة في القسم development من الملف config/database.yml. إذا أردت تنفيذ تهجيرات في بيئة أخرى، في بيئة إنتاج مثلًا، فعليك تحديد ذلك بوضوح عند تنفيذ الأمر:

$ bin/rails db:migrate RAILS_ENV=production

حفظ البيانات في وحدة التحكم

بالرجوع إلى وحدة التحكم للمقالات ArticlesController، علينا تغيير الإجراء create لاستخدام النمط Articles الجديد لحفظ البيانات في قاعدة البيانات. افتح app/controllers/articles_controller.rb وغير الإجراء create ليبدو هكذا:

def create
  @article = Article.new(params[:article])
 
  @article.save
  redirect_to @article
end

إليك ما يحدث: كل نمط ريلز يمكن تهيئته بخاصياته التي يمتلكها والتي تعيَّن تلقائيًا إلى أعمدة قاعدة بيانات المقابلة. في السطر الأول قمنا بذلك فقط (تذكر أن [params[:article يحتوي الخاصيات التي نهتم بها). يكون السطر article.save@ بعد ذلك مسؤولًا عن حفظ النمط في قاعدة البيانات. في النهاية، نعيد توجيه المستخدم إلى الإجراء show، الذي سنُعرفه فيما بعد.

تنبيه: ربما تتساءل لماذا أول حرف في Article.new هو حرف كبير، بينما معظم المراجع الأخرى التي تشير إلى articles في هذا الدليل يكون أول حرف فيها صغيرًا. في هذا الصدد، نحن نشير إلى الصنف Article المُعرف في app/models/article.rb. وأسماء الأصناف في روبي يجب أن تبدأ بحرف كبير.

تنبيه: كما سنرى جميعًا فيما بعد، يعيد article.save@ كائن منطقي (boolean) يُشير فيما إذا حُفظ المقال أم لا.

إذا ذهبت الآن إلى العنوان http://localhost:3000/articles/new، ستقدر على إنشاء مقال. جرب ذلك؛ يجب أن تحصل على خطأ آخر يبدو بالشكل التالي:

الخطأ المتعلق بإجراءات الأمان الذي سيظهر بعد الضغط على زر الإرسال.
الخطأ المتعلق بإجراءات الأمان الذي سيظهر بعد الضغط على زر الإرسال.

يمتلك ريلز العديد من ميزات الأمان التي تُساعدك على كتابة تطبيقات آمنة، وهذا ما تعرضت له الآن. تسمى هذه الميزة "المعاملات القوية" (strong parameters)، التي تطلب أن نخبر ريلز بالضبط أي المعاملات مسموح بها في إجراءات وحدة التحكم.

لماذا عليك أن تهتم؟ إن القدرة على انتزاع وتعيين كل معاملات وحدة التحكم تلقائيًا إلى النمط الخاص بك دفعةً واحدة تُسهل مهمة المبرمج، ولكن يترتَّب على هذه الراحة تمكن الآخرين من فعل أشياء ضارة وخبيثة في تطبيقك. ماذا لو تم إنشاء طلب إلى الخادم ليبدو وكأنه نموذج جديدة للمقال، ولكن يتضمن أيضًا حقولًا إضافية ذات قيم تنتهك سلامة تطبيقك؟ سيعملون على إسناد قيم (قد تكون جيدة مثلًا) بكم هائل إلى النمط الخاص بك، ثم إلى قاعدة البيانات ما قد يدمر تطبيقك أو يؤدي إلى حصول ما هو أسوأ.

يجب عليك وضع معاملات وحدة التحكم في القائمة البيضاء لمنع حصول ثغرة "الإسناد المُكتَّل" (Mass assignment vulnerability). في هذه الحالة، نريد أن نسمح ونطلب العنوان والمعاملات النصية لاستخدام فعال للإجراء create. قواعد الصياغة لهذا توفر require و permit. التغيير سيتضمن سطر واحد في الإجراء create وهو:

@article = Article.new(params.require(:article).permit(:title, :text))

ياخذ هذا عادًة في الحسبان تابعه الخاص لإعادة استخدامه في العديد من الإجراءات لنفس وحدة التحكم، مثل الإجراءان create و update. بعيدًا عن مشاكل الإسناد المُكتَّل، يُجعَل التابع دائمًا خاص (private) للتأكد من عدم استدعائه خارج السياق المراد له. تظهر النتيجة كالتالي:

def create
  @article = Article.new(article_params)
 
  @article.save
  redirect_to @article
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

تنبيه: لمعلومات أكثر، ألقِ نظرة على قسم المعاملات القوية وهذا المقال الذي يتحدث أيضًا عن المعاملات القوية.

إظهار المقالات

إذا أرسلت النموذج ثانيةً الآن، سيشكو ريلز أنه لا يجد الإجراء show. هذا ليس مفيدًا جدًا، لذا لنضيف الإجراء show قبل المتابعة.

كما رأينا في الناتج bin/rails routes، فالمسار للإجراء show كالتالي:

article GET    /articles/:id(.:format)      articles#show

قاعدة الصياغة الخاصة id: تُخبر ريلز أن هذا المسار يتوقع المعامل id:، وفي حالتنا سيكون المُعرف لكل مقال.

كما فعلنا بالسابق، علينا إضافة الإجراء show في الملف app/controllers/articles_controller.rb وواجهته الخاصة به.

ملاحظة: هناك ممارسة متكررة بوضع الإجراءات CRUD في كل وحدة تحكم بالترتيب التالي: index، ثم show، ثم new، ثم edit، ثمَّ create، ثمَّ update، ثمَّ destroy. ربما تستخدم أي ترتيب تختاره ولكن تذكر أن هذه توابع عامة؛ كما ذُكر سابقًا في هذا الدليل، يجب وضعهم قبل التصريح عن الكلمة المفتاحية private التي تتعلق بالمرئية (visibility) في وحدة التحكم.

بهذه المعطيات، دعنا نُضيف الإجراء show كالتالي:

class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
  end
 
  def new
  end
 
  # snippet for brevity

يجب أن تنتبه لأمرين؛ نستخدم Article.find لإيجاد المقال المهتمين به، مرورًا بـ [params[:id للحصول على المعامل id: من الطلب. نستخدم أيضًا متغير لحظي (مسبوق بالرمز @) للإبقاء على مرجع للكائن article. نقوم بذلك لأنَّ ريلز يمرِّر جميع المتغيرات اللحظية للواجهة. الآن، أنشئ الملف app/views/articles/show.html.erb جديد بالمحتوى التالي:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

بهذا التغيير، يجب أن تقدر على إنشاء مقالات جديدة. ألقِ نظرة على http://localhost:3000/articles/new وحاول القيام بذلك.

الناتج الذي يجب أن يظهر بعد إضافة الإجراء show وتحديث الصفحة.
الناتج الذي يجب أن يظهر بعد إضافة الإجراء show وتحديث الصفحة.

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

إنشاء قائمة بكل المقالات

نحتاج لطريقة لعمل قائمة بكل مقالاتنا، لذا لنقوم بالأمر. المسار الموجه كما ظهر في مخرجات الأمر bin/rails routs هو:

articles GET    /articles(.:format)          articles#index

أضف الإجراء index المقابل إلى ذلك المسار داخل وحدة التحكم للمقالات ArticlesController في الملف app/controllers/articles_controller.rb. عندما نكتب إجراء index، الممارسة المعتادة أن تضعها أولًا (في بداية التوابع) في وحدة التحكم. دعنا نقوم بذلك:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
 
  def show
    @article = Article.find(params[:id])
  end
 
  def new
  end
 
  # snippet for brevity

وأخيرًا، أضف الواجهة لهذا الإجراء، ومحلها app/views/articles/index.html.erb:

<h1>Listing articles</h1>
 
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
  </tr>
 
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
    </tr>
  <% end %>
</table>

الآن إذا ألقيت نظرة على http://localhost:3000/articles سترى قائمة بكل المقالات التي أنشأتها.

إضافة روابط

يمكنك الآن إنشاء وإظهار وعمل قائمة بالمقالات. الآن، دعنا نضيف بعض الروابط للتنقل بين الصفحات.

افتح الملف app/views/welcome/index.html.erb وعدلها كالتالي:

<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>

التابع link_to أحد مساعدي الواجهة المدمجين بريلز. يُنشئ رابطًا بناءً على النص المراد عرضه وإلى أين يراد الذهاب؛ في هذه الحالة، يراد الذهاب إلى مسار كل مقال جديد يُنشَأ. دعنا نضيف روابط إلى الواجهات الأخرى أيضًا؛ بدايةً، أضف الرابط "New Article" هذا إلى app/views/articles/index.html.erb بوضعه فوق الوسم <table>:

<%= link_to 'New article', new_article_path %>

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

<%= form_with scope: :article, url: articles_path, local: true do |form| %>
  ...
<% end %>
 
<%= link_to 'Back', articles_path %>

وأخيرً، أضف رابطًا إلى القالب app/views/articles/show.html.erb للعودة إلى الإجراء index أيضًا، حتى يتمكن لمن يشاهد مقالًا واحدًا أن يعود إلى القائمة ويشاهد قائمة المقالات كلها مجددًا:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<%= link_to 'Back', articles_path %>

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

تنبيه: في وضع التطوير (وهو ما تعمل عليه افتراضيًا)، يُعيد ريلز تحميل تطبيقك مع كل طلب مُتصفح، لذا ليس عليك أن تتوقف وتعيد بدء خادم الويب عند كل تغيير.

إضافة بعض التحقق

ملف النمط app/models/article.rb بكل بساطة هو:

class Article < ApplicationRecord
end

ليس هناك الكثير لإضافته في هذا الملف ولكن لاحظ أن الصنف Article يرث من ApplicatioRecord. و ApplicationRecord يرث من ActiveRecord::Base التي تزود أنماط (models) ريلز بوظائف كثيرة بالمجان، بما في ذلك، عمليات قاعدة البيانات الأساسية CRUD (إنشاء (Create) وقراءة (Read) وتحديث (Update) وتدمير (Destroy)) والتحقق من البيانات بالإضافة إلى دعم البحث المعقد والقدرة على الربط بين النماذج المتعددة بعضها ببعض.

يشمل ريلز توابع للمساعدة باعتماد البيانات التي تُرسلها إلى الأنماط.

افتح الملف app/models/article.rb وعدله بالشكل التالي:

class Article < ApplicationRecord
  validates :title, presence: true,
                    length: { minimum: 5 }
end

ستضمن هذه التعديلات أنَّ لكل المقالات عنوان بطول لا يقل عن 5 أحرف. تتحقق ريلز من شروط متنوعة للنمط ما بما في ذلك، وجود أو انفراد الأعمدة وصيغتها ووجود الكائنات المصاحبة (associated objects). هنالك دليل يتحدث عن عمليات التحقق بالتفصيل تجده هنا. بوجود التحقق الآن، عندما تستدعي article.save@ لمقال غير محقق لأحد الشروط التي وضعتها، فسيعيد إليك القيمة false. إذا فتحت الملف app/controllers/articles_controller.rb مرة أخرى، سترى أننا لم نتحقق من القيمة التي يعيدها article.save@ داخل الإجراء create. إذا فشل article.save@ في هذه الحالة (أي أعاد القيمة false)، علينا أن نُظهر النموذج للمستخدم مجدَّدًا لكي يعيد إدخال البيانات بالشكل الصحيح. للقيام بذلك، غيّر الإجراءان new و create داخل الملف app/controllers/articles_controller.r للتالي:

def new
  @article = Article.new
end
 
def create
  @article = Article.new(article_params)
 
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

الإجراء new ينشئ مُتغير نسخة (instance variable) جديد يدعى article@، وسترى لما فعلنا هذا في ما يلي.

لاحظ أنَّ بداخل الإجراء create نستخدم render بدلًا من redirect_to عندما يعيد save القيمة false. التابع render يُستخدم حتى يُمرَّر الكائن article@ إلى القالب new عند تصييره. عملية التصيير (rendering) هذه تتم ضمن نفس الطلب عند إرسال النموذج، بينما redirect_to سيخبر المتصفح بإصدار طلب جديد.

إذا أعدت تحميل الصفحة http://localhost:3000/articles/new وحاولت حفظ مقال بدون عنوان، فستعيدك ريلز إلى النموذج مجدَّدًا، ولكن هذا ليس مفيد جدًا. عليك بإخبار المستخدم أن هناك شيء خطأ. لعمل ذلك، سنغير الملف app/views/articles/new.html.erb للتحقق من رسائل الخطأ.

<%= form_with scope: :article, url: articles_path, local: true do |form| %>
 
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
 
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
 
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
 
  <p>
    <%= form.submit %>
  </p>
 
<% end %>
 
<%= link_to 'Back', articles_path %>

القليل من الأشياء تحدث هنا. نتحقق من وجود أي أخطاء مع ‎@article.errors.any?‎ وفي تلك الحالة، نُظهِر قائمة بكل الأخطاء من خلال ‎@article.errors.full_messages.

إنَّ pluralize هو مساعد ريلز ويأخذ عددًا وسلسلةً نصيةً كمعاملات. إذا كان العدد أكبر من 1، فستُجمَّع السلسلة النصية تلقائيًا.

السبب من إضافة article = Article.new@ في وحدة التحكم ArticlesController أنه لولا ذلك لأصبحت قيمة article@ هي nil في واجهتنا واستدعاء ?article.errors.any@ سيرمي خطأً.

تنبيه: يغلِّف ريلز تلقائيًا الحقول التي تحتوي على خطأ بعنصر من النوع <div> مع تعيين الصنف field_with_errors إليه. يمكنك تعريف قاعدة CSS لإظهاره.

الآن، ستحصل على رسالة خطأ عندما تحفظ مقالًا بدون عنوان؛ جرب حفظ مقال جديد عبر الدخول إلى الصفحة http://localhost:3000/articles/new وحفظ نموذج دون عنوان:

الخطأ الظاهر عند حفظ مقال دون عنوان أو لا عدم تحقيق عنوانه شرطًا محدَّدًا وهو كون عدد حروفه أقل من 5.
الخطأ الظاهر عند حفظ مقال دون عنوان أو لا عدم تحقيق عنوانه شرطًا محدَّدًا وهو كون عدد حروفه أقل من 5.

تحديث المقالات

لقد غطينا جانبي الإنشاء والقراءة (الحرفين C و R) من العمليات CRUD. دعنا الآن أن نركز على جانب تحديث المقالات (الحرف U).

الخطوة الأولى التي سنتخذها هي إضافة الإجراء edit إلى وحدة التحكم ArticlesController عادةً بين الإجرائين create و new كما هو موضح:

def new
  @article = Article.new
end
 
def edit
  @article = Article.find(params[:id])
end
 
def create
  @article = Article.new(article_params)
 
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

الواجهة ستحتوي على نموذجٍ مشابهٍ للنموذج المستخدم عند إنشاء مقالات جديدة. أنشئ ملفًا يُسمى app/views/articles/edit.html.erb وأضف فيه الأسطر التالية:

<h1>Edit article</h1>
 
<%= form_with(model: @article, local: true) do |form| %>
 
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
 
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
 
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
 
  <p>
    <%= form.submit %>
  </p>
 
<% end %>
 
<%= link_to 'Back', articles_path %>

في هذا الوقت، نوجه النموذج للإجراء update، الذي لم يُعرَّف بعد ولكنه سيُعرف قريبًا.

تمرير الكائن article إلى التابع سينشئ تلقائيًا عنوان url لإرسال النموذج للمقال المُعدّل. يُخبر هذا الاختيار ريلز أننا نريد لهذا النموذج أن يرسل من خلال الطريقة PATCH HTTP وهي طريقة HTTP المتوقع ان تستعملها عند تحديث موارد طبقًا للبروتوكول REST.

المعاملات الخاصة بالتابع form_with قد تكون كائنات النمط (model)، لنقل هي model: @article والذي سيتسبب في تعبئة المساعد للنموذج بحقول الكائن. تمرير نطاق رمز (symbol scope مثل scope::article) يُنشئ فقط الحقول ولكن بدون تعبئة أي شيء بهم. لتفاصيل أكثر ألقِ نظرة على توثيق التابع form_with.

بعد ذلك، علينا إنشاء الإجراء update في الملف app/controllers/articles_controller.rb. أضفه بين الإجراء create والتابع private:

def create
  @article = Article.new(article_params)
 
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end
 
def update
  @article = Article.find(params[:id])
 
  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

التابع الجديد update يُستخدَم عندما تُريد تحديث سجلٍ موجود بالفعل، ويقبل جدول hash يحتوي على الخاصيات التي تُريد تحديثها. كما في السابق، إذا كان هناك خطأ في تحديث المقال الذي تُريده، أظهر مجدَّدًا النموذج للمستخدم (مثل الخطوات السابقة).

نُعيد استخدام التابع article_params الذي عرفناه سابقًا للإجراء create.

تنبيه: ليس من الضروري تمرير كل الخاصيات إلى الإجراء update. على سبيل المثال، إذا تم استدعاء

('article.update(title: 'A new title@، سيُحدث ريلز الخاصية title دون المساس بالخاصيات الأخرى.

في النهاية، نُريد أن نُظهر الرابط للإجراء edit في قائمة كل المقالات، لذا دعنا نُضيف ذلك الآن إلى الملف app/views/articles/index.html.erb لجعله يظهر بجانب الرابط "show":

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="2"></th>
  </tr>
 
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
    </tr>
  <% end %>
</table>

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

...
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

وهذا ما يبدو عليه التطبيق الآن:

شكل قائمة المقالات الناتجة بعد إضافة زر التعديل لكل مقال فيها.
شكل قائمة المقالات الناتجة بعد إضافة زر التعديل لكل مقال فيها.

إزالة التكرار في عرض الواجهات

صفحة التعديل edit تبدو مشابهة للصفحة new؛ في الحقيقة، كلاهما يتشارك نفس الشيفرة لعرض النموذج. دعنا نُزيل هذا التكرار باستخدام العرض الجزئي (view partial). عادةً، ما تبدأ الملفات الجزئية بشرطة سفلية.

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

أنشئ ملفًا جديدًا باسم app/views/articles/_form.html.erb بالمحتوى التالي:

<%= form_with model: @article, local: true do |form| %>
 
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
 
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>
 
  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>
 
  <p>
    <%= form.submit %>
  </p>
 
<% end %>

كل شيء يبقى كما هو ما عدا التصريح عن التابع form_with. سبب استخدامنا لهذا التصريح البسيط عن التابع form_with ليمثل أيًا من النموذجين هو أنَّ resource@ هو مورد (resource) يقابل مجموعة كاملة من المسارات RESTful الموجهة، ويتمكن ريلز من الاستدلال على أي عنوان URL أو تابع يراد استخدامه. لمزيد من المعلومات عن استخدام form_with، ألقِ نظرة على هذه الصفحة. الآن، دعنا نُحدث الواجهة app/views/articles/new.html.erb لاستخدام هذا الملف الجزئي الجديد، وإعادة كتابته تمامًا.

<h1>New article</h1>
 
<%= render 'form' %>
 
<%= link_to 'Back', articles_path %>

ثم افعل الشيء نفسه للواجهة app/views/articles/edit.html.erb:

<h1>Edit article</h1>
 
<%= render 'form' %>
 
<%= link_to 'Back', articles_path %>

حذف المقالات

نحن الآن مستعدون لتغطية جزء الحذف (الحرف D) من العمليات CRUD الذي يشمل حذف المقالات من قاعدة البيانات. باتباع المبدأ REST، سيكون المسار لمسح المقالات تبعًا للناتج bin/rails routes:

DELETE /articles/:id(.:format)      articles#destroy

التابع الموجِه delete يجب استخدامه في المسارات الموجَّهة لتدمير موارد (resources). إذا تُرك ذلك كمسار get النموذجي، قد يصنع الناس عناوين URL ضارة مثل ما يلي:

<a href='http://example.com/articles/1/destroy'>look at this cat!</a>

نستخدم التابع delete لتدمير موارد، وهذا المسار الموجه (route) يُعيَّن للإجراء destroy داخل الملف app/controllers/articles_controller.rb، الغير موجود بعد. التابع destroy هو أخر الإجراءات CRUD في وحدة التحكم؛ ومثل الإجراءات العامة الأخرى من CRUD، يجب أن يوضع قبل أي توابع خاصة (private) أو محمية (protected). دعنا نُضيفه الآن:

def destroy
  @article = Article.find(params[:id])
  @article.destroy
 
  redirect_to articles_path
end

وحدة التحكم ArticlesController الكاملة في الملف app/controllers/articles_controller.rb يجب أن تبدو الآن هكذا:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
 
  def show
    @article = Article.find(params[:id])
  end
 
  def new
    @article = Article.new
  end
 
  def edit
    @article = Article.find(params[:id])
  end
 
  def create
    @article = Article.new(article_params)
 
    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end
 
  def update
    @article = Article.find(params[:id])
 
    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end
 
  def destroy
    @article = Article.find(params[:id])
    @article.destroy
 
    redirect_to articles_path
  end
 
  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

يُستدعَى التابع destroy من كائنات Active Record عندما يراد مسحهم من قاعدة البيانات. لاحظ أننا لا نُريد إضافة واجهة لهذا الإجراء ولكن سنعيد التوجيه إلى الإجراء index. في النهاية، أضف الرابط 'Destroy' إلى قالب الإجراء index (أي ضمن الملف app/views/articles/index.html.erb) لتغليف كل شيء معًا:

<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>
 
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
      <td><%= link_to 'Destroy', article_path(article),
              method: :delete,
              data: { confirm: 'Are you sure?' } %></td>
    </tr>
  <% end %>
</table>

هنا نستخدم link_to بطريقة أخرى. نُمرِّر المسار الموجه المُسمى كمعامل ثاني، والخيارات كمعامل آخر. تُستعمَل الخيارات method: :delete و data: { confirm: 'Are you sure?' }‎ كخاصيات HTML5 وبذلك عند الضغط على الرابط، سيُظهر ريلز نافذة تأكيد للمستخدم، ثم يرسل الرابط مع التابع delete. يُنفَّذ هذا عبر ملف JavaScript وهو rails-ujs المُضمَّن تلقائيًا في تخطيط تطبيقك (الملف app/views/layouts/application.html.erb) عندما ولدت التطبيق. بدون هذا الملف، لن تظهر نافذة التأكيد.

ظهور نافذة لتأكيد عملية حذف مقالٍ عند الضغط على الزر Destroy.
ظهور نافذة لتأكيد عملية حذف مقالٍ عند الضغط على الزر Destroy.

تنبيه: تعلم أكثر عن جافاسكربت الواضحة من دليل العمل مع JavaScript في ريلز. هنيئًا لك! يمكنك الآن إنشاء وإظهار وعمل قائمة وتحديث وحذف المقالات.

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

إضافة نمطٍ ثانٍ

حان الوقت لإضافة نمط (model) آخر للتطبيق. النموذج الآخر سيتعامل مع التعليقات على تضاف إلى المقالات.

توليد نمط

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

$ bin/rails generate model Comment commenter:string body:text article:references

هذا الأمر سيولد أربع ملفات:

الملف الغرض منه
db/migrate/20140120201010_create_comments.rb التهجير لإنشاء جدول التعليقات في قاعدة البيانات الخاصة بك (اسم المعلِّق سيشمل بصمة وقت أخرى).
app/models/comment.rb النمط Comment.
test/models/comment_test.rb عمليات الاختبار (Testing harness، أو يدعى أيضًا اطار الاختبار الآلي [automated test framework]) للنمط Comment.
test/fixtures/comments.yml عينة التعليقات لاستخدامها في الاختبار.

أولًا، ألقى نظرة على app/models/comment.rb:

class Comment < ApplicationRecord
  belongs_to :article
end

هذا شبيه جدًا بالنمط Article الذي رأيته من قبل. الاختلاف يكمن في السطر belongs_to :article، الذي يُهيئ ارتباط Active Record. ستتعلم قليلًا عن الارتباطات في القسم التالي من هذا الدليل.

الكلمة المفتاحية (reference:) تُستخدم في الأمر bash هو نوع خاص من البيانات للأنماط. تُنشئ عمودًا جديدًا في جدول قاعدة البيانات الخاص بك مع اسم النمط (model) المعطى مضافًا إليها id_ الذي يأخذ قيمًا صحيحةً. لفهم أفضل، حلل الملف db/schema.rb بعد تشغيل التهجير.

بالإضافة إلى النمط، يقوم ريلز بتهجير أيضًا لإنشاء جدول قاعدة البيانات المقابل:

class CreateComments < ActiveRecord::Migration[5.0]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, foreign_key: true
 
      t.timestamps
    end
  end
end

يُنشئ السطر t.references عمودًا لقيم عددية صحيحة يُسمى article_id، وفهرس له وقيد رئيسي خارجي يُشير إلى العمود id للجدول articles. اذهب ونفذ التهجير الآن:

$ bin/rails db:migrate

إن ريلز ذكي كفاية لينفذ فقط التهجيرات التي لم تُنفَّذ بالفعل لقاعدة البيانات الحالية؛ في هذه الحالة سترى فقط:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

ربط الأنماط

ارتباطات Active Record تسمح لك بالتصريح بسهولة عن العلاقة بين نمطين (models). في حالة التعليقات والمقالات، يمكنك أن تكتب العلاقات بهذه الطريقة:

  • كل تعليق ينتمي إلى مقال واحد
  • قد يحتوي المقال الواحد على الكثير من التعليقات

في الحقيقة، هذا قريب جدًا من قواعد الصياغة التى يستخدمها ريلز للتصريح عن هذا الارتباط. لقد رأيت بالفعل سطرًا من الشيفرة داخل النمط Comment (وهو app/models/comment.rb) الذي يجعل كل تعليق ينتمي إلى مقال:

class Comment < ApplicationRecord
  belongs_to :article
end

ستحتاج إلى تعديل app/models/article.rb لإضافة الجانب الأخر من الارتباط:

class Article < ApplicationRecord
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

هذان التصريحان يفعِّلان سلوكًا آليًّا يعمل إلى حد جيد. على سبيل المثال، إذا كنت تمتلك متغير النسخة artilce@ يحوي مقالةً، فيُمكنك استرداد جميع التعليقات المنتمية إلى ذلك المقال في مصفوفة باستخدام article.comments@.

تنبيه: لمعلومات أكثر عن ارتباطات Active Record، ألقِ نظرة على دليل ارتباطات Active Record.

إضافة مسار توجيه للتعليقات

كما في وحدة التحكم Welcome، علينا أن نُضيف مسار ليعرف ريلز أين تُريد أن تنتقل لترى التعليقات. افتح الملف config/routes.rb ثانيةً وعدله بالشكل التالي:

resources :articles do
  resources :comments
end

يُنشيء هذا المورد comments كمورد مُتشعب ضمن articles. هذا جزء آخر للإمساك بالعلاقة الهرمية الموجودة بين المقالات والتعليقات.

تنبيه: لمعلومات أكثر عن التوجيه (routing)، ألقِ نظرة على دليل التوجيه من الخارج والداخل في ريلز.

توليد وحدة تحكم

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

$ bin/rails generate controller Comments

هذا يُنشيء خمسة ملفات ومجلد فارغ:

الملف/الدليل الغرض منه
app/controllers/comments_controller.rb وحدة التحكم Comments.
app/views/comments/ واجهات وحدة التحكم تُخزن هنا.
test/controllers/comments_controller_test.rb اختبار وحدة التحكم.
app/helpers/comments_helper.rb ملف مساعد الواجهة.
app/assets/javascripts/comments.coffee لغة CoffeeScript لوحدة التحكم.
app/assets/stylesheets/comments.scss ملف CSS لوحدة التحكم.

كما في أي مدونة، سيكتب قرائنا تعليقاتهم مباشرة بعد قراءة المقال؛ وبعد إضافة تعليقهم، سيُرسلون إلى صفحة إظهار المقال ليروا تعليقهم مُدرج. لهذا، توفر وحدة تحكم التعليقات CommentsController تابعًا لإنشاء تعليقات ومسح تلك المُزعجة عندما يفتحون المقال.

لذا أولًا، سنفعل قالب إظهار المقال (الملف app/views/articles/show.html.erb) ليسمح لنا بعمل تعليق جديد:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

سيضيف هذا نموذجًا إلى صفحة إظهار المقال التي تُنشئ تعليقًا جديدًا باستدعاء الإجراء create الخاص بوحدة التحكم CommentsController. الاستدعاء form_with هنا يستخدم مصفوفةً تبني مسار توجيه مُتشعب مثل articles/1/comments/. دعنا نشغل الإجراء create في الملف app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

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

بالإضافة إلى ذلك، تأخذ الشيفرة بعضًا من التوابع المتاحة للارتباط. يمكننا استعمال التابع create في article.comments@ لإنشاء وحفظ التعليق. هذا سيربط التعليق تلقائيًا لينتمي إلى المقال المعين.

عند إنشاء تعليق جديد، نرسل المستخدم مجدَّدًا للمقال الأصلي باستخدام المساعد (article_path(@article. كما رأينا بالفعل، هذا سيستدعي الإجراء show من وحدة التحكم ArticleController الذي سيصيِّر بدوره القالب show.html.erb. هذا حيث نُريد إظهار التعليق، لذا لنُضِف ذلك إلى الملف app/views/articles/show.html.erb:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
 
  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>
 
<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

الآن، يمكنك أن تُضيف مقالات وتعليقات إلى مدونتك بحيث يظهرون في المواضع الصحيحة.

ظهور قسم أسفل المقال لإضافة تعليقات إليه.
ظهور قسم أسفل المقال لإضافة تعليقات إليه.

إعادة هيكلة

الآن، بما أنَّه لدينا مقالات وتعليقات تعمل بشكل صحيح، لنلقِ نظرة على القالب app/views/articles/show.html.erb؛ ستجده طويلًأ وغير ملائم. يمكنك استخدام ملفات جزئية لمعالجة ذلك.

تصيير المجموعات الجزئية

بدايةً، سننشئ ملف تعليق جزئي لإظهار جميع التعليقات للمقال. أنشئ الملف app/views/comments/_comment.html.erb وضع التالي به:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

ومن ثم عدل الملف app/views/articles/show.html.erb ليبدو بالشكل التالي:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<%= render @article.comments %>
 
<h2>Add a comment:</h2>
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

الآن، هذا سيصيِّر الملف الجزئي app/views/comments/_comment.html.erb مرةً لكل تعليق في المجموعة article.comments@. بما أنَّ التابع render يتكرر خلال المجموعة article.comments@، يخصص كل تعليق لمتغير محلي له نفس اسم الملف الجزئي؛ في هذه الحالة، سيكون التابع comment متاح لنا في الملف الجزئي لنظهره.

تصيير نموذج ملف جزئي

دعنا ننقل قسم التعليقات الجديد لخارج ملفه الجزئي. مرة آخرى، أنشئ ملفًا جديدًا يدعى app/views/comments/_form.html.erb يحتوي على:

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

وثم عدل الملف app/views/articles/show.html.erb لتبدو كالتالي:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<%= render @article.comments %>
 
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

تُعرِّف الكلمة render الثانية قالب الملف الجزئي الذي نُريد تصييره (render)، الذي هو comments/form. إن ريلز ذكي كفايةً ليضع الخط المائل في تلك السلسلة النصية ويلاحظ أنك تريد تصيير الملف form.html.erb_ في المجلد app/views/comments.

الكائن article@ متاح لأي ملف جزئي صُيِّر (rendered) في الواجهة لأننا عرفناه كمتغير نسخة.

مسح التعليقات

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

بدايةً، دعنا نضع رابط المسح في الملف الجزئي app/views/comments/_comment.html.erb:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>
 
<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

بالضغط على رابط مسح التعليق "Destroy Comment" الجديد هذا سيطلق DELETE /articles/:article_id/comments/:id لوحدة التحكم CommentsController الخاصة بنا، ويُستخدم هذا لإيجاد التعليق الذي نود مسحه؛ دعنا نضيف الإجراء destroy لوحدة التحكم الخاصة بنا (في الملف app/controllers/comments_controller.rb):

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

الإجراء destroy سيجد المقال الذي نبحث عنه، ويحدد موضع التعليق داخل المجموعة article.comments@، ثم يمسحه من قاعدة البيانات ويرسلنا للإجراء show الخاص بالمقال.

مسح الكائنات المصاحبة

إذا مسحت مقال، فإن تعليقاته المرتبطة به بحاجة للمسح أيضًا، وإلا سيشغلون مساحة في قاعدة البيانات. يسمح ريلز لك باستخدام الخيار dependent ويعني "التابع" لارتباط ما لتحقيق ذلك. عدل النمط المقال model في الملف Article app/models/article.rbmodel كالتالي:

class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

الأمان

الاستيثاق الأساسي

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

يُقدم ريلز نظامًا بسيطًا جدًا للاستيثاق HTTP والذي سيعمل جيدًا في هذا الموقف.

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

لاستخدام نظام الاستيثاق، نحدِّد ذلك في أعلى وحدة تحكم المقالات ArticlesController في app/controllers/articles_controller.rb. في حالتنا، نُريد من المستخدم أن يكون موثوقًا لكل إجراء باستثناء الإجراء index والإجراء show، لذا نضيف ذلك عبر الشيفرة التالية:

class ArticlesController < ApplicationController
 
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
 
  def index
    @articles = Article.all
  end
 
  # snippet for brevity

نُريد أن نسمح للمستخدمين الموثوقين فقط بمسح التعليقات، لذا في وحدة تحكم التعليقات CommentsController (الملف app/controllers/comments_controller.rb) ونكتب:

class CommentsController < ApplicationController
 
  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
 
  def create
    @article = Article.find(params[:article_id])
    # ...
  end
 
  # snippet for brevity

الآن، إذا حاولت إنشاء مقال جديد، سيُرحب بك تحدي الاستيثاق بنظام HTTP الأساسي:

إضافة تحدي استيثاق HTTP الأساسي لمنع أي أحد غير موثوق من التعديل على التعديل على المقالات وحذفها.
إضافة تحدي استيثاق HTTP الأساسي لمنع أي أحد غير موثوق من التعديل على التعديل على المقالات وحذفها.

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

اعتبارات أمان الأخرى

الأمان، خصوصًا في تطبيقات الويب، منطقةٌ واسعةٌ ومُفصلةٌ. الأمان في تطبيق ريلز الخاص بك مُغطى بعمق أكثر في دليل تأمين تطبيقات ريلز.

ما هو التالي؟

الآن، رأيت تطبيق ريلز الخاص بك الأول؛ لك الحرية المطلقة لتحديثه وتجربته بنفسك.

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

التهيئات المطلوبة والمشكلات المحتملة

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

إذا أخطأت في هذا الشأن، فالعَرَضْ الشائع هو ظهور ماسة سوداء (black diamond) بداخلها علامة استفهام تظهر في المُتصفح. عَرَضٌ أخر، هي ظهور رموز غير مفهومة مثل "ü"  تظهر بدلًا من "ü". يأخذ ريلز عدد من الخطوات الداخلية لمعالجة الأسباب الشائعة لتلك المشاكل التي يمكن اكتشافها وتصحيحها تلقائيًا. ولكن إذا كان لديك بيانات خارجية غير مخزنة بالترميز UTF-8، ربما تتسبب عرضيًا في ظهور تلك الأنواع من المشاكل التي لا تُكتشَف تلقائيًا أو تُصحَّح بواسطة ريلز.

مصدران شائعان جدًا للبيانات التي ليست مرمزة بالترميز UTF-8:

  • محرر النصوص الذي تعمل عليه: معظم محرري النصوص (مثل TextMate)، افتراضيًا يحفظون الملفات بالترميز UTF-8. إذا لم يكن محررك كذلك، سينتج عن ذلك رموز خاصة تُدخلها في قوالبك (مثل é) وهو ما يؤدي إلى ظهور ماسة بداخلها علامة استفهام في المُتصفح. ينطبق هذا أيضًا على ملفات الترجمة i18n. معظم محررات النصوص التي لا تستعمل الترميز UTF-8 افتراضيًّا (مثل بعض الإصدارات من Dreamweaver) توفر طريقة لتغيير الترميز الافتراضي إلى UTF-8. ننصحك بشدة بفعل ذلك.
  • قاعدة بياناتك: يُحول ريلز البيانات من قاعدة بياناتك إلى الترميز UTF-8 افتراضيًا. ولكن  إذا لم تكن قاعدة بياناتك تستخدم الترميز UTF-8 داخليًا، ربما لا تقدر على حفظ كل الرموز التي يُدخلها مستخدموك. على سبيل المثال، إذا كانت قاعدة بياناتك تستخدم الرموز اللاتينية (الترميز Latin-1) داخليًا، ويُدخل مستخدموك رمز روسي أو عبري أو ياباني، فستضيع البيانات إلى الأبد بمجرد دخولها في قاعدة البيانات. إذا أمكن، استخدم الترميز UTF-8 للتخزين الداخلي لقاعدة البيانات.

مصادر