البدء مع ريلز
يغطي هذا الدليل بدء وتشغيل ريلز. بعد قراءة هذا الدليل، ستتعلم:
- كيف تثبّت ريلز وتُنشئ تطبيق ريلز جديد وتربط تطبيق ريلز بقاعدة بيانات.
- التخطيط العام لتطبيق ريلز.
- المبادئ الأساسية للنمط 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 وسترى خطأ بالمسار:
يحدث ذلك الخطأ لأن المسار بحاجة إلى وحدة تحكم مُعرفة بغرض خدمة الطلب. حل تلك المشكلة المحددة بسيط: أنشئ وحدة تحكم تُسمى ArticlesController
. يمكنك القيام بذلك بتنفيذ الأمر التالي في سطر الأوامر:
$ bin/rails generate controller Articles
إذا فتحت ملف وحدة التحكم app/controllers/articles_controller.rb المولد حديثًا، سترى وحدة تحكم فارغةً تمامًا:
class ArticlesController < ApplicationController
end
وحدة التحكم هي ببساطة صنف يُعرَّف ليرث من ApplicationController
. بداخل ذلك الصنف ستُعرف التوابع التي ستُصبح الإجراءات لوحدة التحكم. تلك الإجراءات ستقوم بعمليات CRUD على المقالات بداخل نظامك.
ملاحظة: هناك توابع عامة وخاصة ومحمية في روبي، ولكن التوابع العامة فقط تستطيع أن تكون إجراءات لوحدات التحكم. لمزيد من المعلومات راجع برمجة روبي.
إذا حدثت http://localhost:3000/articles/new الآن، ستجد خطأ جديدًا:
يُشير هذا الخطأ إلى أنَّ ريلز لا يستطيع إيجاد الإجراء الجديد بداخل وحدة التحكم ArticlesController
التي أنشأتها للتو. هذا بسبب أن وحدات التحكم المولدة في ريلز تكون فارغةً في الحالة الافتراضية، إلا إذا بلغت عن الإجراءات المراد إنشاؤها في عملية التوليد.
لتعريف الإجراءات يدويًا، كل ما عليك هو تعريف تابع جديد داخل وحدة التحكم. افتح الملف app/controllers/articles_controller.rb بداخل الصنف ArticlesController
، ثمَّ عرف التابع الجديد. ستبدو وحدة التحكم الخاصة بك بالشكل التالي:
class ArticlesController < ApplicationController
def new
end
end
بتعريف التابع الجديد في ArticlesController
، إذا حدثت الصفحة http://localhost:3000/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 وحاول القيام بذلك.
إن ظهر لك مثل ما هو موضح في الصورة، فالأمور تجري على خير. ممتاز! واصل التقدم.
إنشاء قائمة بكل المقالات
نحتاج لطريقة لعمل قائمة بكل مقالاتنا، لذا لنقوم بالأمر. المسار الموجه كما ظهر في مخرجات الأمر 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 وحفظ نموذج دون عنوان:
تحديث المقالات
لقد غطينا جانبي الإنشاء والقراءة (الحرفين 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) عندما ولدت التطبيق. بدون هذا الملف، لن تظهر نافذة التأكيد.
تنبيه: تعلم أكثر عن جافاسكربت الواضحة من دليل العمل مع 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 الأساسي:
هناك توابع استيثاق أخرى متاحة لتطبيقات ريلز. إضافتان مشهورتان للاستيثاق لريلز هما Devise و Authlogic بالإضافة لعدد من الإضافات الأخرى.
اعتبارات أمان الأخرى
الأمان، خصوصًا في تطبيقات الويب، منطقةٌ واسعةٌ ومُفصلةٌ. الأمان في تطبيق ريلز الخاص بك مُغطى بعمق أكثر في دليل تأمين تطبيقات ريلز.
ما هو التالي؟
الآن، رأيت تطبيق ريلز الخاص بك الأول؛ لك الحرية المطلقة لتحديثه وتجربته بنفسك.
تذكر، ليس عليك القيام بكل شيء بدون مساعدة. لأنك تحتاج إلى المساعدة في بدء وتشغيل ريلز، أشعر بالحرية لطلب الاستشارة من تلك الموارد الداعمة:
- قناة rubyonrails# على irc.freenode.net.
- دورة تطوير تطبيقات الويب باستخدام لغة روبي وإطار ريلز.
التهيئات المطلوبة والمشكلات المحتملة
أسهل طريقة للعمل مع ريلز هي أن تُخزن كل البيانات الخارجية بترميز 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 للتخزين الداخلي لقاعدة البيانات.