الفرق بين المراجعتين ل"Rails/getting started"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
ط (مراجعة وتدقيق.)
 
(مراجعة متوسطة واحدة بواسطة نفس المستخدم غير معروضة)
سطر 1: سطر 1:
 +
<noinclude>{{DISPLAYTITLE:البدء مع ريلز}}</noinclude>
 +
[[تصنيف:Rails]]
 +
[[تصنيف:Rails Getting Started]]
 
يغطي هذا الدليل بدء وتشغيل ريلز. بعد قراءة هذا الدليل، ستتعلم:
 
يغطي هذا الدليل بدء وتشغيل ريلز. بعد قراءة هذا الدليل، ستتعلم:
 
* كيف تثبّت ريلز وتُنشئ تطبيق ريلز جديد وتربط تطبيق ريلز بقاعدة بيانات.
 
* كيف تثبّت ريلز وتُنشئ تطبيق ريلز جديد وتربط تطبيق ريلز بقاعدة بيانات.
سطر 21: سطر 24:
 
تتضمن فلسفة ريلز مبدأين توجيهيين أساسيين:
 
تتضمن فلسفة ريلز مبدأين توجيهيين أساسيين:
 
* '''لا تكرر نفسك''': هو مبدأ تطوير للبرمجيات ينص على أن "كل جزء من المعرفة يجب أن يكون له تمثيل واحد، لا لبس فيه وموثَّق في النظام." من خلال عدم كتابة نفس المعلومات مرارًا وتكرارًا، لتكون شيفراتنا أكثر قابلية للإصلاح  وللاستزادة، وأقل أخطاءً.
 
* '''لا تكرر نفسك''': هو مبدأ تطوير للبرمجيات ينص على أن "كل جزء من المعرفة يجب أن يكون له تمثيل واحد، لا لبس فيه وموثَّق في النظام." من خلال عدم كتابة نفس المعلومات مرارًا وتكرارًا، لتكون شيفراتنا أكثر قابلية للإصلاح  وللاستزادة، وأقل أخطاءً.
* '''العرف قبل الضبط''': لدى ريلز آراء حول أفضل طريقة للقيام بأشياء كثيرة في تطبيق ويب تجعل مجموعة الأعراف (conventions) تلك في أوضاع افتراضية، بدلًا من أن تتطلب منك تحديد التفاصيل من خلال ملفات إعدادات لا نهاية لها.
+
* '''العرف فوق الضبط''': لدى ريلز آراء حول أفضل طريقة للقيام بأشياء كثيرة في تطبيق ويب تجعل مجموعة الأعراف (conventions) تلك في أوضاع افتراضية، بدلًا من أن تتطلب منك تحديد التفاصيل من خلال ملفات إعدادات لا نهاية لها.
  
 
== إنشاء مشروع جديد بإطار العمل ريلز ==
 
== إنشاء مشروع جديد بإطار العمل ريلز ==
سطر 138: سطر 141:
 
سيؤدي ذلك إلى إطلاق Puma، وهو خادم ويب يُوزّع باستخدام ريلز بشكل افتراضي. للاطلاع على تطبيقك قيد التنفيذ، افتح نافذة المتصفح وانتقل إلى العنوان [http://localhost:3000/ http://localhost:3000]. يجب أن تشاهد صفحة معلومات ريلز الافتراضية:
 
سيؤدي ذلك إلى إطلاق Puma، وهو خادم ويب يُوزّع باستخدام ريلز بشكل افتراضي. للاطلاع على تطبيقك قيد التنفيذ، افتح نافذة المتصفح وانتقل إلى العنوان [http://localhost:3000/ http://localhost:3000]. يجب أن تشاهد صفحة معلومات ريلز الافتراضية:
  
<nowiki>[[ملف:rails_welcome.png|بديل=صفحة ريلز الترحيبية|بدون|تصغير|500بك|صفحة ريلز الترحيبية]]</nowiki>
+
[[ملف:rails_welcome.png|بديل=صفحة ريلز الترحيبية|بدون|تصغير|500بك|صفحة ريلز الترحيبية]]
  
 
'''تنبيه''': لإيقاف خادم الويب، اضغط على Ctrl + C في نافذة الطرفية حيث يُشغّل الخادم. للتحقق من توقف الخادم، يجب أن ترى مؤشر محثّ الأوامر مرة أخرى. بالنسبة لمعظم الأنظمة الشبيهة بيونكس، بما في ذلك نظام التشغيل ماك، سيكون ظهور الرمز <code>$</code> كافيًا للدالالة على توقفه. في وضع التطوير (development mode)، لا يتطلب ريلز عادةً إعادة تشغيل الخادم؛ سيسري مفعول التغييرات التي تجريها على الملفات تلقائيًا في الخادم.
 
'''تنبيه''': لإيقاف خادم الويب، اضغط على Ctrl + C في نافذة الطرفية حيث يُشغّل الخادم. للتحقق من توقف الخادم، يجب أن ترى مؤشر محثّ الأوامر مرة أخرى. بالنسبة لمعظم الأنظمة الشبيهة بيونكس، بما في ذلك نظام التشغيل ماك، سيكون ظهور الرمز <code>$</code> كافيًا للدالالة على توقفه. في وضع التطوير (development mode)، لا يتطلب ريلز عادةً إعادة تشغيل الخادم؛ سيسري مفعول التغييرات التي تجريها على الملفات تلقائيًا في الخادم.
سطر 191: سطر 194:
 
   root 'welcome#index'
 
   root 'welcome#index'
 
end
 
end
</syntaxhighlight>يخبر السطر <code>'root 'welcome#index</code> ريلز بتعيين طلبات جذر التطبيق إلى وحدة التحكم Welcome للإجراء index وتخبر الشيفرة <code>'get 'welcome/index</code> ريلز بتعيين الطلبات http://localhost:3000/welcome/index إلى وحدة التحكم Welcome للإجراء index. أُنشيء هذا مُسبقًا عندما شغلت مولد وحدة التحكم (لتنفيذ الأمر <code>bin/rails generate controller Welcome index</code>).
+
</syntaxhighlight>يخبر السطر <code>'root 'welcome#index</code> ريلز بتعيين طلبات جذر التطبيق إلى وحدة التحكم Welcome للإجراء <code>index</code> وتخبر الشيفرة <code>'get 'welcome/index</code> ريلز بتعيين الطلبات http://localhost:3000/welcome/index إلى وحدة التحكم Welcome للإجراء index. أُنشيء هذا مُسبقًا عندما شغلت مولد وحدة التحكم (لتنفيذ الأمر <code>bin/rails generate controller Welcome index</code>).
  
 
شغل خادم الويب مرة أخرى إذا أوقفته لتوليد وحدة التحكم (عبر الأمر <code>bin/rails</code>
 
شغل خادم الويب مرة أخرى إذا أوقفته لتوليد وحدة التحكم (عبر الأمر <code>bin/rails</code>
سطر 228: سطر 231:
 
</syntaxhighlight>في القسم التالي، ستُضيف إمكانية إنشاء مقالات جديدة في تطبيقك وستتمكن من عرضهم. هذا ما يشير له الحرف "C" (إنشاء) و"R" (قراءة) من CRUD. سيبدو شكل النموذج الذي جرى تنفيذه وفقًا لما سبق بالشكل التالي:
 
</syntaxhighlight>في القسم التالي، ستُضيف إمكانية إنشاء مقالات جديدة في تطبيقك وستتمكن من عرضهم. هذا ما يشير له الحرف "C" (إنشاء) و"R" (قراءة) من CRUD. سيبدو شكل النموذج الذي جرى تنفيذه وفقًا لما سبق بالشكل التالي:
  
<nowiki>[[ملف:new_article.png|بديل=شكل نموذج إضافة المقالات الجديدة الذي أُنشِئ والذي يراد عرضه في تطبيق المدونة.|بدون|تصغير|500بك|شكل نموذج إضافة المقالات الجديدة الذي أُنشِئ والذي يراد عرضه في تطبيق المدونة.]]</nowiki>
+
[[ملف:new_article.png|بديل=شكل نموذج إضافة المقالات الجديدة الذي أُنشِئ والذي يراد عرضه في تطبيق المدونة.|بدون|تصغير|500بك|شكل نموذج إضافة المقالات الجديدة الذي أُنشِئ والذي يراد عرضه في تطبيق المدونة.]]
  
 
=== إرساء الأساسيات ===
 
=== إرساء الأساسيات ===
 
بدايًة، تحتاج إلى مكان داخل التطبيق لتنشئ مقالًا جديدًا. سيكون العنوان articles/new/ مكانًا جيدًا لذلك. مع مسار التوجيه المُعرَّف مسبقًا، يُمكن إنشاء الطلبات للعنوان articles/new/ داخل التطبيق. انتقل إلى العنوان http://localhost:3000/articles/new وسترى خطأ بالمسار:
 
بدايًة، تحتاج إلى مكان داخل التطبيق لتنشئ مقالًا جديدًا. سيكون العنوان articles/new/ مكانًا جيدًا لذلك. مع مسار التوجيه المُعرَّف مسبقًا، يُمكن إنشاء الطلبات للعنوان articles/new/ داخل التطبيق. انتقل إلى العنوان http://localhost:3000/articles/new وسترى خطأ بالمسار:
  
<nowiki>[[ملف:routing_error_no_controller.png|بديل=خطأ التوجيه الذي سيظهر عند الانتقال إلى العنوان ‎/articles/new في تطبيق المدونة دون وجود وحدة تحكم تخدمه.|بدون|تصغير|500بك|خطأ التوجيه الذي سيظهر عند طلب العنوان ‎/articles/new في تطبيق المدونة دون وجود وحدة تحكم تخدمه.]]</nowiki>
+
[[ملف:routing_error_no_controller.png|بديل=خطأ التوجيه الذي سيظهر عند الانتقال إلى العنوان ‎/articles/new في تطبيق المدونة دون وجود وحدة تحكم تخدمه.|بدون|تصغير|500بك|خطأ التوجيه الذي سيظهر عند طلب العنوان ‎/articles/new في تطبيق المدونة دون وجود وحدة تحكم تخدمه.]]
  
 
يحدث ذلك الخطأ لأن المسار بحاجة إلى وحدة تحكم مُعرفة بغرض خدمة الطلب. حل تلك المشكلة المحددة بسيط: أنشئ وحدة تحكم تُسمى <code>ArticlesController</code>. يمكنك القيام بذلك بتنفيذ الأمر التالي في سطر الأوامر:<syntaxhighlight lang="shell">
 
يحدث ذلك الخطأ لأن المسار بحاجة إلى وحدة تحكم مُعرفة بغرض خدمة الطلب. حل تلك المشكلة المحددة بسيط: أنشئ وحدة تحكم تُسمى <code>ArticlesController</code>. يمكنك القيام بذلك بتنفيذ الأمر التالي في سطر الأوامر:<syntaxhighlight lang="shell">
سطر 246: سطر 249:
 
إذا حدثت http://localhost:3000/articles/new الآن، ستجد خطأ جديدًا:
 
إذا حدثت http://localhost:3000/articles/new الآن، ستجد خطأ جديدًا:
  
<nowiki>[[ملف:unknown_action_new_for_articles.png|بديل=ظهور خطأ جديد بعد توليد وحدة التحكم ArticlesController وتحديث الصفحة ‎/articles/new.|بدون|تصغير|500بك|ظهور خطأ جديد بعد توليد وحدة التحكم ArticlesController وتحديث الصفحة ‎/articles/new.]]</nowiki>
+
[[ملف:unknown_action_new_for_articles.png|بديل=ظهور خطأ جديد بعد توليد وحدة التحكم ArticlesController وتحديث الصفحة ‎/articles/new.|بدون|تصغير|500بك|ظهور خطأ جديد بعد توليد وحدة التحكم ArticlesController وتحديث الصفحة ‎/articles/new.]]
  
 
يُشير هذا الخطأ إلى أنَّ ريلز لا يستطيع إيجاد الإجراء الجديد بداخل وحدة التحكم <code>ArticlesController</code> التي أنشأتها للتو. هذا بسبب أن وحدات التحكم المولدة في ريلز تكون فارغةً في الحالة الافتراضية، إلا إذا بلغت عن الإجراءات المراد إنشاؤها في عملية التوليد.
 
يُشير هذا الخطأ إلى أنَّ ريلز لا يستطيع إيجاد الإجراء الجديد بداخل وحدة التحكم <code>ArticlesController</code> التي أنشأتها للتو. هذا بسبب أن وحدات التحكم المولدة في ريلز تكون فارغةً في الحالة الافتراضية، إلا إذا بلغت عن الإجراءات المراد إنشاؤها في عملية التوليد.
سطر 257: سطر 260:
 
</syntaxhighlight>بتعريف التابع الجديد في <code>ArticlesController</code>، إذا حدثت الصفحة <nowiki>http://localhost:3000/articles/new</nowiki> ستجد خطأ أخر:
 
</syntaxhighlight>بتعريف التابع الجديد في <code>ArticlesController</code>، إذا حدثت الصفحة <nowiki>http://localhost:3000/articles/new</nowiki> ستجد خطأ أخر:
  
<nowiki>[[ملف:template_is_missing_articles_new.jpeg|بديل=الخطأ الجديد الذي سيظهر بعد تعريف التابع new الجديد في الصنف ArticlesController وتحديث الصفحة ‎/articles/new.|بدون|تصغير|500بك|الخطأ الجديد الذي سيظهر بعد تعريف التابع new الجديد في الصنف ArticlesController وتحديث الصفحة ‎/articles/new.]]</nowiki>
+
[[ملف:template_is_missing_articles_new.jpeg|بديل=الخطأ الجديد الذي سيظهر بعد تعريف التابع new الجديد في الصنف ArticlesController وتحديث الصفحة ‎/articles/new.|بدون|تصغير|500بك|الخطأ الجديد الذي سيظهر بعد تعريف التابع new الجديد في الصنف ArticlesController وتحديث الصفحة ‎/articles/new.]]
  
 
لقد تلقيت هذا الخطأ الآن نظرًا لأن ريلز يتوقع أن تكون الإجراءات لديها واجهات مرتبطة بها لعرض معلوماتها. مع عدم وجود طريقة عرض، سيعيد ريلز استثناء.
 
لقد تلقيت هذا الخطأ الآن نظرًا لأن ريلز يتوقع أن تكون الإجراءات لديها واجهات مرتبطة بها لعرض معلوماتها. مع عدم وجود طريقة عرض، سيعيد ريلز استثناء.
سطر 322: سطر 325:
 
بتعريف النموذج والمسار المُرتبط به، ستتمكن الآن من ملء النموذج والضغط على زر الإرسال لبدء عملية إنشاء مقال جديد، لذا قم بعمل ذلك. وعند إرسال النموذج، سيظهر لك خطأ مشابه لما هو موضح في الصورة التالية:
 
بتعريف النموذج والمسار المُرتبط به، ستتمكن الآن من ملء النموذج والضغط على زر الإرسال لبدء عملية إنشاء مقال جديد، لذا قم بعمل ذلك. وعند إرسال النموذج، سيظهر لك خطأ مشابه لما هو موضح في الصورة التالية:
  
<nowiki>[[ملف:unknown_action_create_for_articles.png|بديل=رسالة الخطأ التي ستظهر عند الضغط على زر الإرسال في النموذج.|بدون|تصغير|500بك|رسالة الخطأ التي ستظهر عند الضغط على زر الإرسال في النموذج.]]</nowiki>
+
[[ملف:unknown_action_create_for_articles.png|بديل=رسالة الخطأ التي ستظهر عند الضغط على زر الإرسال في النموذج.|بدون|تصغير|500بك|رسالة الخطأ التي ستظهر عند الضغط على زر الإرسال في النموذج.]]
  
 
يجب عليك الآن إنشاء الإجراء <code>create</code> الخاص بوحدة التحكم <code>ArticlesController</code> لكي يعمل زر الإرسال.
 
يجب عليك الآن إنشاء الإجراء <code>create</code> الخاص بوحدة التحكم <code>ArticlesController</code> لكي يعمل زر الإرسال.
سطر 357: سطر 360:
 
يستجيب ريلز بإنشاء مجموعة من الملفات. إلى الآن، نهتم فقط بالملف app/models/article.rb وdb/migrate/20140120191729_create_articles.rb (ربما يكون الاسم لديك مختلف قليلًا). الأخير مسئول عن إنشاء بنية قواعد البيانات، والتي سننظر في أمرها فيما بعد.
 
يستجيب ريلز بإنشاء مجموعة من الملفات. إلى الآن، نهتم فقط بالملف app/models/article.rb وdb/migrate/20140120191729_create_articles.rb (ربما يكون الاسم لديك مختلف قليلًا). الأخير مسئول عن إنشاء بنية قواعد البيانات، والتي سننظر في أمرها فيما بعد.
  
'''تنبيه''': [[Rails/active record basics|السجل الفعَّال]] (Active Record) ذكي كفاية لتعيين أسماء العمود تلقائيًا إلى خاصيات النمط، ما يعني أنه ليس عليك التصريح عن الخاصيات بداخل أنماط ريلز، لأن هذا سيتم تلقائيًا بواسطة [[Rails/active record basics|السجل الفعَّال]].
+
'''تنبيه''': [[Rails/active record|Active Record]] ذكي كفاية لتعيين أسماء العمود تلقائيًا إلى خاصيات النمط، ما يعني أنه ليس عليك التصريح عن الخاصيات بداخل أنماط ريلز، لأن هذا سيتم تلقائيًا بواسطة [[Rails/active record|Active Record]].
 
=== القيام بتهجير ===
 
=== القيام بتهجير ===
 
كما رأينا، فإن الأمر <code>bin/rails generate model</code> أنشئ ملف تهجير لقاعدة البيانات (database migration) داخل المجلد db/migrate. التهجيرات هي من أصناف روبي المصممة لتسهيل إنشاء وتعديل جداول قاعدة البيانات. يستخدم ريلز أوامر rake للقيام بالتهجيرات، ومن الممكن إلغاء تهجير بعد تطبيقه على قاعدة البيانات. أسماء ملفات التهجير تشمل  بصمة الوقت (timestamp) لضمان تنفيذها بالترتيب التي أنشأت به.
 
كما رأينا، فإن الأمر <code>bin/rails generate model</code> أنشئ ملف تهجير لقاعدة البيانات (database migration) داخل المجلد db/migrate. التهجيرات هي من أصناف روبي المصممة لتسهيل إنشاء وتعديل جداول قاعدة البيانات. يستخدم ريلز أوامر rake للقيام بالتهجيرات، ومن الممكن إلغاء تهجير بعد تطبيقه على قاعدة البيانات. أسماء ملفات التهجير تشمل  بصمة الوقت (timestamp) لضمان تنفيذها بالترتيب التي أنشأت به.
سطر 374: سطر 377:
 
</syntaxhighlight>سينشئ التهجير بالأعلى تابعًا يُسمى <code>change</code> والذي سيُستدعَى عند القيام بهذا التهجير. الإجراء المُعرف في هذا التابع يمكن الرجوع عنه أيضًا، ما يعني أن ريلز تعرف كيف ترجع عن أي تغيير أجري عبر هذا التهجير، إن أردت الرجوع عنه فيما بعد. عند القيام بهذا التهجير، سينشئ جدولًا باسم articles بعمود سلسلة نصية (string) واحد وعمود نصي (text). وسينشئ أيضًا حقلي بصمتي وقت (timestamp) مما يسمح لريلز بتتبع وقت إنشاء المقال وأوقات إجراء التحديث.
 
</syntaxhighlight>سينشئ التهجير بالأعلى تابعًا يُسمى <code>change</code> والذي سيُستدعَى عند القيام بهذا التهجير. الإجراء المُعرف في هذا التابع يمكن الرجوع عنه أيضًا، ما يعني أن ريلز تعرف كيف ترجع عن أي تغيير أجري عبر هذا التهجير، إن أردت الرجوع عنه فيما بعد. عند القيام بهذا التهجير، سينشئ جدولًا باسم articles بعمود سلسلة نصية (string) واحد وعمود نصي (text). وسينشئ أيضًا حقلي بصمتي وقت (timestamp) مما يسمح لريلز بتتبع وقت إنشاء المقال وأوقات إجراء التحديث.
  
'''تنبيه''': لمعلومات أكثر عن التهجيرات، ألقِ نظرة على دليل [[Rails/active record migrations|تهجير السجل الفعال]].
+
'''تنبيه''': لمعلومات أكثر عن التهجيرات، ألقِ نظرة على دليل [[Rails/active record migrations|تهجيرات Active Record]].
 
في تلك الأثناء، يمكن استعمال الأمر <code>bin/rails</code> للقيام بالتهجير:<syntaxhighlight lang="rails">
 
في تلك الأثناء، يمكن استعمال الأمر <code>bin/rails</code> للقيام بالتهجير:<syntaxhighlight lang="rails">
 
$ bin/rails db:migrate
 
$ bin/rails db:migrate
سطر 395: سطر 398:
 
end
 
end
 
</syntaxhighlight>إليك ما يحدث: كل نمط ريلز يمكن تهيئته بخاصياته التي يمتلكها والتي تعيَّن تلقائيًا إلى أعمدة قاعدة بيانات المقابلة. في السطر الأول قمنا بذلك فقط (تذكر أن <code>[params[:article</code> يحتوي الخاصيات التي نهتم بها). يكون السطر <code>article.save@</code> بعد ذلك مسؤولًا عن حفظ النمط في قاعدة البيانات. في النهاية، نعيد توجيه المستخدم إلى الإجراء <code>show</code>، الذي سنُعرفه فيما بعد.
 
</syntaxhighlight>إليك ما يحدث: كل نمط ريلز يمكن تهيئته بخاصياته التي يمتلكها والتي تعيَّن تلقائيًا إلى أعمدة قاعدة بيانات المقابلة. في السطر الأول قمنا بذلك فقط (تذكر أن <code>[params[:article</code> يحتوي الخاصيات التي نهتم بها). يكون السطر <code>article.save@</code> بعد ذلك مسؤولًا عن حفظ النمط في قاعدة البيانات. في النهاية، نعيد توجيه المستخدم إلى الإجراء <code>show</code>، الذي سنُعرفه فيما بعد.
 +
 
'''تنبيه''': ربما تتساءل لماذا أول حرف في <code>Article.new</code> هو حرف كبير، بينما معظم المراجع الأخرى التي تشير إلى <code>articles</code> في هذا الدليل يكون أول حرف فيها صغيرًا. في هذا الصدد، نحن نشير إلى الصنف <code>Article</code> المُعرف في app/models/article.rb. وأسماء الأصناف في روبي يجب أن تبدأ بحرف كبير.
 
'''تنبيه''': ربما تتساءل لماذا أول حرف في <code>Article.new</code> هو حرف كبير، بينما معظم المراجع الأخرى التي تشير إلى <code>articles</code> في هذا الدليل يكون أول حرف فيها صغيرًا. في هذا الصدد، نحن نشير إلى الصنف <code>Article</code> المُعرف في app/models/article.rb. وأسماء الأصناف في روبي يجب أن تبدأ بحرف كبير.
  
سطر 401: سطر 405:
 
إذا ذهبت الآن إلى العنوان http://localhost:3000/articles/new<nowiki/>، ستقدر على إنشاء مقال. جرب ذلك؛ يجب أن تحصل على خطأ آخر يبدو بالشكل التالي:
 
إذا ذهبت الآن إلى العنوان http://localhost:3000/articles/new<nowiki/>، ستقدر على إنشاء مقال. جرب ذلك؛ يجب أن تحصل على خطأ آخر يبدو بالشكل التالي:
  
<nowiki>[[ملف:forbidden_attributes_for_new_article.png|بديل=الخطأ المتعلق بإجراءات الأمان الذي سيظهر بعد الضغط على زر الإرسال.|بدون|تصغير|500بك|الخطأ المتعلق بإجراءات الأمان الذي سيظهر بعد الضغط على زر الإرسال.]]</nowiki>
+
[[ملف:forbidden_attributes_for_new_article.png|بديل=الخطأ المتعلق بإجراءات الأمان الذي سيظهر بعد الضغط على زر الإرسال.|بدون|تصغير|500بك|الخطأ المتعلق بإجراءات الأمان الذي سيظهر بعد الضغط على زر الإرسال.]]
  
 
يمتلك ريلز العديد من ميزات الأمان التي تُساعدك على كتابة تطبيقات آمنة، وهذا ما تعرضت له الآن. تسمى هذه الميزة "[[Rails/action controller overview#.D8.A7.D9.84.D9.85.D8.B9.D8.A7.D9.85.D9.84.D8.A7.D8.AA .D8.A7.D9.84.D9.82.D9.88.D9.8A.D8.A9|المعاملات القوية]]" (strong parameters)، التي تطلب أن نخبر ريلز بالضبط أي المعاملات مسموح بها في إجراءات وحدة التحكم.
 
يمتلك ريلز العديد من ميزات الأمان التي تُساعدك على كتابة تطبيقات آمنة، وهذا ما تعرضت له الآن. تسمى هذه الميزة "[[Rails/action controller overview#.D8.A7.D9.84.D9.85.D8.B9.D8.A7.D9.85.D9.84.D8.A7.D8.AA .D8.A7.D9.84.D9.82.D9.88.D9.8A.D8.A9|المعاملات القوية]]" (strong parameters)، التي تطلب أن نخبر ريلز بالضبط أي المعاملات مسموح بها في إجراءات وحدة التحكم.
سطر 429: سطر 433:
 
</syntaxhighlight>قاعدة الصياغة الخاصة <code>id:</code> تُخبر ريلز أن هذا المسار يتوقع المعامل <code>id:</code>، وفي حالتنا سيكون المُعرف لكل مقال.
 
</syntaxhighlight>قاعدة الصياغة الخاصة <code>id:</code> تُخبر ريلز أن هذا المسار يتوقع المعامل <code>id:</code>، وفي حالتنا سيكون المُعرف لكل مقال.
  
كما فعلنا بالسابق، علينا إضافة الإجراء show في الملف app/controllers/articles_controller.rb وواجهته الخاصة به.
+
كما فعلنا بالسابق، علينا إضافة الإجراء <code>show</code> في الملف app/controllers/articles_controller.rb وواجهته الخاصة به.
  
 
'''ملاحظة''': هناك ممارسة متكررة بوضع الإجراءات CRUD في كل وحدة تحكم بالترتيب التالي: <code>index</code>، ثم <code>show</code>، ثم <code>new</code>، ثم <code>edit</code>، ثمَّ <code>create</code>، ثمَّ <code>update</code>، ثمَّ <code>destroy</code>. ربما تستخدم أي ترتيب تختاره ولكن تذكر أن هذه توابع عامة؛ كما ذُكر سابقًا في هذا الدليل، يجب وضعهم قبل التصريح عن الكلمة المفتاحية <code>private</code> التي تتعلق بالمرئية (visibility) في وحدة التحكم.
 
'''ملاحظة''': هناك ممارسة متكررة بوضع الإجراءات CRUD في كل وحدة تحكم بالترتيب التالي: <code>index</code>، ثم <code>show</code>، ثم <code>new</code>، ثم <code>edit</code>، ثمَّ <code>create</code>، ثمَّ <code>update</code>، ثمَّ <code>destroy</code>. ربما تستخدم أي ترتيب تختاره ولكن تذكر أن هذه توابع عامة؛ كما ذُكر سابقًا في هذا الدليل، يجب وضعهم قبل التصريح عن الكلمة المفتاحية <code>private</code> التي تتعلق بالمرئية (visibility) في وحدة التحكم.
سطر 456: سطر 460:
 
</syntaxhighlight>بهذا التغيير، يجب أن تقدر على إنشاء مقالات جديدة. ألقِ نظرة على http://localhost:3000/articles/new وحاول القيام بذلك.
 
</syntaxhighlight>بهذا التغيير، يجب أن تقدر على إنشاء مقالات جديدة. ألقِ نظرة على http://localhost:3000/articles/new وحاول القيام بذلك.
  
<nowiki>[[ملف:show_action_for_articles.png|بديل=الناتج الذي يجب أن يظهر بعد إضافة الإجراء show وتحديث الصفحة.|بدون|تصغير|500بك|الناتج الذي يجب أن يظهر بعد إضافة الإجراء show وتحديث الصفحة.]]</nowiki>
+
[[ملف:show_action_for_articles.png|بديل=الناتج الذي يجب أن يظهر بعد إضافة الإجراء show وتحديث الصفحة.|بدون|تصغير|500بك|الناتج الذي يجب أن يظهر بعد إضافة الإجراء show وتحديث الصفحة.]]
  
 
إن ظهر لك مثل ما هو موضح في الصورة، فالأمور تجري على خير. ممتاز! واصل التقدم.
 
إن ظهر لك مثل ما هو موضح في الصورة، فالأمور تجري على خير. ممتاز! واصل التقدم.
سطر 611: سطر 615:
 
الآن، ستحصل على رسالة خطأ عندما تحفظ مقالًا بدون عنوان؛ جرب حفظ مقال جديد عبر الدخول إلى الصفحة http://localhost:3000/articles/new وحفظ نموذج دون عنوان:
 
الآن، ستحصل على رسالة خطأ عندما تحفظ مقالًا بدون عنوان؛ جرب حفظ مقال جديد عبر الدخول إلى الصفحة http://localhost:3000/articles/new وحفظ نموذج دون عنوان:
  
<nowiki>[[ملف:form_with_errors.png|بديل=الخطأ الظاهر عند حفظ مقال دون عنوان أو لا عدم تحقيق عنوانه شرطًا محدَّدًا وهو كون عدد حروفه أقل من 5.|بدون|تصغير|500بك|الخطأ الظاهر عند حفظ مقال دون عنوان أو لا عدم تحقيق عنوانه شرطًا محدَّدًا وهو كون عدد حروفه أقل من 5.]]</nowiki>
+
[[ملف:form_with_errors.png|بديل=الخطأ الظاهر عند حفظ مقال دون عنوان أو لا عدم تحقيق عنوانه شرطًا محدَّدًا وهو كون عدد حروفه أقل من 5.|بدون|تصغير|500بك|الخطأ الظاهر عند حفظ مقال دون عنوان أو لا عدم تحقيق عنوانه شرطًا محدَّدًا وهو كون عدد حروفه أقل من 5.]]
  
 
=== تحديث المقالات ===
 
=== تحديث المقالات ===
سطر 732: سطر 736:
 
<%= link_to 'Back', articles_path %>
 
<%= link_to 'Back', articles_path %>
 
</syntaxhighlight>وهذا ما يبدو عليه التطبيق الآن:
 
</syntaxhighlight>وهذا ما يبدو عليه التطبيق الآن:
<nowiki>[[ملف:index_action_with_edit_link.png|بديل=شكل قائمة المقالات الناتجة بعد إضافة زر التعديل لكل مقال فيها.|بدون|تصغير|500بك|شكل قائمة المقالات الناتجة بعد إضافة زر التعديل لكل مقال فيها.]]</nowiki>
+
 
 +
[[ملف:index_action_with_edit_link.png|بديل=شكل قائمة المقالات الناتجة بعد إضافة زر التعديل لكل مقال فيها.|بدون|تصغير|500بك|شكل قائمة المقالات الناتجة بعد إضافة زر التعديل لكل مقال فيها.]]
 
=== إزالة التكرار في عرض الواجهات ===
 
=== إزالة التكرار في عرض الواجهات ===
 
صفحة التعديل <code>edit</code> تبدو مشابهة للصفحة <code>new</code>؛ في الحقيقة، كلاهما يتشارك نفس الشيفرة لعرض النموذج. دعنا نُزيل هذا التكرار باستخدام العرض الجزئي (view partial). عادةً، ما تبدأ الملفات الجزئية بشرطة سفلية.
 
صفحة التعديل <code>edit</code> تبدو مشابهة للصفحة <code>new</code>؛ في الحقيقة، كلاهما يتشارك نفس الشيفرة لعرض النموذج. دعنا نُزيل هذا التكرار باستخدام العرض الجزئي (view partial). عادةً، ما تبدأ الملفات الجزئية بشرطة سفلية.
سطر 769: سطر 774:
 
   
 
   
 
<% end %>
 
<% end %>
</syntaxhighlight>كل شيء يبقى كما هو ما عدا التصريح عن التابع <code>form_with</code>. سبب استخدامنا لهذا التصريح البسيط عن التابع <code>form_with</code> ليمثل أيًا من النموذجين هو أنَّ <code>resource@</code> هو مورد (resource) يقابل مجموعة كاملة من المسارات RESTful الموجهة، ويقدر ريلز على الاستدلال على أي عنوان URL أو تابع يراد استخدامه. لمزيد من المعلومات عن استخدام <code>form_with</code> هذا، ألقِ نظرة على [http://api.rubyonrails.org/v5.2.2/classes/ActionView/Helpers/FormHelper.html#method-i-form_with-label-Resource-oriented+style هذه] الصفحة.
+
</syntaxhighlight>كل شيء يبقى كما هو ما عدا التصريح عن التابع <code>form_with</code>. سبب استخدامنا لهذا التصريح البسيط عن التابع <code>form_with</code> ليمثل أيًا من النموذجين هو أنَّ <code>resource@</code> هو مورد (resource) يقابل مجموعة كاملة من المسارات RESTful الموجهة، ويتمكن ريلز من الاستدلال على أي عنوان URL أو تابع يراد استخدامه. لمزيد من المعلومات عن استخدام <code>form_with</code>، ألقِ نظرة على [http://api.rubyonrails.org/v5.2.2/classes/ActionView/Helpers/FormHelper.html#method-i-form_with-label-Resource-oriented+style هذه] الصفحة.
 
 
الآن، دعنا نُحدث الواجهة app/views/articles/new.html.erb لاستخدام هذا الملف الجزئي الجديد، وإعادة كتابته تمامًا.
 
{| class="wikitable"
 
|<nowiki><h1>New article</h1></nowiki>
 
  
 +
الآن، دعنا نُحدث الواجهة app/views/articles/new.html.erb لاستخدام هذا الملف الجزئي الجديد، وإعادة كتابته تمامًا.<syntaxhighlight lang="html">
 +
<h1>New article</h1>
 +
 
<%= render 'form' %>
 
<%= render 'form' %>
 
+
 
<%= link_to 'Back', articles_path %>
 
<%= link_to 'Back', articles_path %>
|}
+
</syntaxhighlight>ثم افعل الشيء نفسه للواجهة app/views/articles/edit.html.erb:<syntaxhighlight lang="html">
ثم أفعل نفس الشيء للواجهة app/views/articles/edit.html.erb:
+
<h1>Edit article</h1>
{| class="wikitable"
+
|<nowiki><h1>Edit article</h1></nowiki>
 
 
 
 
<%= render 'form' %>
 
<%= render 'form' %>
 
+
 
<%= link_to 'Back', articles_path %>
 
<%= link_to 'Back', articles_path %>
|}
+
</syntaxhighlight>
 
 
=== مسح المقالات ===
 
نحن الآن مستعدون لتغطية جزء المسح "D" من العمليات CRUD، مسح المقالات من قاعدة البيانات. باتباع المبدأ REST، المسار لمسح المقالات تبعُا للناتج bin/rails routes سيكون:
 
{| class="wikitable"
 
|DELETE /articles/:id(.:format)   articles#destroy
 
|}
 
التابع الموجِه delete يجب استخدامه في مسارات لتدمير موارد. إذا تُرك ذلك كمسار get النموذجي، قد يصنع الناس عناوين URLS  ضارة كهذا:
 
{| class="wikitable"
 
|<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
 
|}
 
نستخدم التابع delete لتدمير موارد، وهذا المسار يُعين للإجراء تدمير destroy داخل app/controllers/articles_controller.rb، الذي لم يوجد بعد. التابع تدمير destroy هو أخر الإجراءات CRUD في وحدة التحكم، وكالإجراءات العامة الأخرى من CRUD، يجب أن يوضع قبل أي توابع خاصة private أو protected محمية. دعنا نُضيفه: 
 
  
{| class="wikitable"
+
=== حذف المقالات ===
|$def destroy
+
نحن الآن مستعدون لتغطية جزء الحذف (الحرف D) من العمليات CRUD الذي يشمل حذف المقالات من قاعدة البيانات. باتباع المبدأ REST، سيكون المسار لمسح المقالات تبعًا للناتج <code>bin/rails routes</code>:<syntaxhighlight lang="text">
 
+
DELETE /articles/:id(.:format)     articles#destroy
 @article = Article.find(params[:id])
 
 
 
 @article.destroy
 
 
 
 redirect_to articles_path
 
  
 +
</syntaxhighlight>التابع الموجِه <code>delete</code> يجب استخدامه في المسارات الموجَّهة لتدمير موارد (resources). إذا تُرك ذلك كمسار <code>get</code> النموذجي، قد يصنع الناس عناوين URL ضارة مثل ما يلي:<syntaxhighlight lang="html">
 +
<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
 +
</syntaxhighlight>نستخدم التابع <code>delete</code> لتدمير موارد، وهذا المسار الموجه (route) يُعيَّن للإجراء <code>destroy</code> داخل الملف app/controllers/articles_controller.rb، الغير موجود بعد. التابع <code>destroy</code> هو أخر الإجراءات CRUD في وحدة التحكم؛ ومثل الإجراءات العامة الأخرى من CRUD، يجب أن يوضع قبل أي توابع خاصة (private) أو محمية (protected). دعنا نُضيفه الآن:<syntaxhighlight lang="rails">
 +
def destroy
 +
  @article = Article.find(params[:id])
 +
  @article.destroy
 +
 +
  redirect_to articles_path
 
end
 
end
|}
+
</syntaxhighlight>وحدة التحكم <code>ArticlesController</code> الكاملة في الملف app/controllers/articles_controller.rb يجب أن تبدو الآن هكذا:<syntaxhighlight lang="rails">
وحدة التحكم للمقالات ArticlesController الكاملة في الملف app/controllers/articles_controller.rb يجب أن تبدو الآن هكذا:
+
class ArticlesController < ApplicationController
{| class="wikitable"
+
  def index
|class ArticlesController < ApplicationController
+
    @articles = Article.all
 
+
  end
 def index
+
 
+
  def show
   @articles = Article.all
+
    @article = Article.find(params[:id])
 
+
  end
 end
+
 
+
  def new
 def show
+
    @article = Article.new
 
+
  end
   @article = Article.find(params[:id])
+
 
+
  def edit
 end
+
    @article = Article.find(params[:id])
 
+
  end
 def new
+
 
+
  def create
   @article = Article.new
+
    @article = Article.new(article_params)
 
+
 end
+
    if @article.save
 
+
      redirect_to @article
 def edit
+
    else
 
+
      render 'new'
   @article = Article.find(params[:id])
+
    end
 
+
  end
 end
+
 
+
  def update
 def create
+
    @article = Article.find(params[:id])
 
+
   @article = Article.new(article_params)
+
    if @article.update(article_params)
 
+
      redirect_to @article
   if @article.save
+
    else
 
+
      render 'edit'
     redirect_to @article
+
    end
 
+
  end
   else
+
 
+
  def destroy
     render 'new'
+
    @article = Article.find(params[:id])
 
+
    @article.destroy
   end
+
 
+
    redirect_to articles_path
 end
+
  end
 
+
 def update
+
  private
 
+
    def article_params
   @article = Article.find(params[:id])
+
      params.require(:article).permit(:title, :text)
 
+
    end
   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
 
end
|}
+
</syntaxhighlight>يُستدعَى التابع <code>destroy</code> من كائنات [[Rails/active record|Active Record]] عندما يراد مسحهم من قاعدة البيانات. لاحظ أننا لا نُريد إضافة واجهة لهذا الإجراء ولكن سنعيد التوجيه إلى الإجراء <code>index</code>.
يُستدعى التابع تدمير destroy من كائنات التسجيل الحي Active Record عندما تُريد مسحهم من قاعدة البيانات. لاحظ أننا لا نُريد إضافة واجهة لهذا الإجراء حيث سنعيد التوجيه إلى الإجراء فهرس index.
 
 
 
في النهاية، أضف الرابط 'تدمير Destroy' إلى قالب الإجراء فهرس index (app/views/articles/index.html.erb) لتغليف كل شيء معًا.
 
{| class="wikitable"
 
|<nowiki><h1>Listing Articles</h1></nowiki>
 
  
 +
في النهاية، أضف الرابط 'Destroy' إلى قالب الإجراء <code>index</code> (أي ضمن الملف app/views/articles/index.html.erb) لتغليف كل شيء معًا:<syntaxhighlight lang="html">
 +
<h1>Listing Articles</h1>
 
<%= link_to 'New article', new_article_path %>
 
<%= 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>
 +
</syntaxhighlight>هنا نستخدم <code>link_to</code> بطريقة أخرى. نُمرِّر المسار الموجه المُسمى كمعامل ثاني، والخيارات كمعامل آخر. تُستعمَل الخيارات <code>method: :delete</code> و <code>data: { confirm: 'Are you sure?' }‎</code> كخاصيات HTML5 وبذلك عند الضغط على الرابط، سيُظهر ريلز نافذة تأكيد للمستخدم، ثم يرسل الرابط مع التابع <code>delete</code>. يُنفَّذ هذا عبر ملف [[JavaScript]] وهو rails-ujs المُضمَّن تلقائيًا في تخطيط تطبيقك (الملف app/views/layouts/application.html.erb) عندما ولدت التطبيق. بدون هذا الملف، لن تظهر نافذة التأكيد.
  
<nowiki><table></nowiki>
+
[[ملف:confirm_dialog.png|بديل=ظهور نافذة لتأكيد عملية حذف مقالٍ عند الضغط على الزر Destroy.|بدون|تصغير|500بك|ظهور نافذة لتأكيد عملية حذف مقالٍ عند الضغط على الزر Destroy.]]
 
 
 <nowiki><tr></nowiki>
 
 
 
   <nowiki><th>Title</th></nowiki>
 
 
 
   <nowiki><th>Text</th></nowiki>
 
 
 
   <nowiki><th colspan="3"></th></nowiki>
 
 
 
 <nowiki></tr></nowiki>
 
 
 
 <% @articles.each do |article| %>
 
 
 
   <nowiki><tr></nowiki>
 
 
 
     <nowiki><td></nowiki><%= article.title %><nowiki></td></nowiki>
 
 
 
     <nowiki><td></nowiki><%= article.text %><nowiki></td></nowiki>
 
 
 
     <nowiki><td></nowiki><%= link_to 'Show', article_path(article) %><nowiki></td></nowiki>
 
 
 
     <nowiki><td></nowiki><%= link_to 'Edit', edit_article_path(article) %><nowiki></td></nowiki>
 
 
 
     <nowiki><td></nowiki><%= link_to 'Destroy', article_path(article),
 
 
 
             method: :delete,
 
 
 
             data: { confirm: 'Are you sure?' } %><nowiki></td></nowiki>
 
 
 
   <nowiki></tr></nowiki>
 
 
 
 <% end %>
 
  
<nowiki></table></nowiki>
+
'''تنبيه''': تعلم أكثر عن جافاسكربت الواضحة من دليل [[Rails/working with javascript in rails|العمل مع JavaScript في ريلز]].
|}
+
هنيئًا لك! يمكنك الآن إنشاء وإظهار وعمل قائمة وتحديث وحذف المقالات.
هنا نستخدم link_to بطريقة أخرى. نُمرر المسار المُسمى كمعامل argument ثاني، والخيارات كمعامل ثاني. ثم الخيارات كمعامل آخر. الخيارات  method: :delete و{'?data: {confirm: 'Are you sure تُستخدم كلواحق HTML5 حتى عند الضغط على الرابط، سيُظهر ريلز نافذة تأكيد حوارية للمستخدم، ثم يرسل الرابط بالتابع delete. هذا مٌشمل في ملف الـ JavaScript وهو rails-ujs المشمول تلقائيًا في تخطيط تطبيقك (app/views/layouts/application.html.erb) عندما ولدت التطبيق. بدون هذا الملف، لن يظهر صندوق التأكيد الحواري.
 
{| class="wikitable"
 
|تنبيه: تعلم أكثر عن الـ JavaScript غير الظاهرة من دليل العمل مع الـ JavaScript في ريلز.
 
|}
 
مبروك، يمكنك الآن إنشاء وإظهار وعمل قائمة وتحديث وتدمير المقالات.
 
{| class="wikitable"
 
|تنبيه: عمومًا،يُشجع ريلز على استخدام كائنات موارد بدلًا من إعلان المسارات يدويًا. لمعلومات أكثر عن التوجيه، ألقى نظرة على توثيق التوجيه.
 
|}
 
  
== إضافة نموذج ثاني ==
+
'''تنبيه''': عمومًا، يُشجع ريلز على استخدام كائنات موارد بدلًا من التصريح عن المسارات يدويًا. لمعلومات أكثر عن مسارات التوجيه، ألقِ نظرة على دليل [[Rails/routing|التوجيه من الخارج والداخل]].
حان الوقت لإضافة نموذج آخر للتطبيق. النموذج الآخر سيتعامل مع التعليقات على المقالات.
+
== إضافة نمطٍ ثانٍ ==
 +
حان الوقت لإضافة نمط (model) آخر للتطبيق. النموذج الآخر سيتعامل مع التعليقات على تضاف إلى المقالات.
  
=== توليد نموذج ===
+
=== توليد نمط ===
سنذهب لنرى نفس المولد الذي استخدمناه من قبل عند إنشاء نموذج المقال Article. هذه المرة سننشئ نموذج التعليق comment ليكون مرجع لمقال. نفذ سطر الأوامر هذا في الطرفية:
+
سنرى نفس المولد الذي استخدمناه من قبل عند إنشاء النمط <code>Article</code>. هذه المرة سننشئ النمط <code>Comment</code> ليكون مرجعًا لمقال. نفذ الأمر التالي في الطرفية:<syntaxhighlight lang="shell">
{| class="wikitable"
+
$ bin/rails generate model Comment commenter:string body:text article:references
|$ bin/rails generate model Comment commenter:string body:text article:references
+
</syntaxhighlight>هذا الأمر سيولد أربع ملفات:
|}
 
هذا الأمر سيولد أربع ملفات:
 
 
{| class="wikitable"
 
{| class="wikitable"
|الملف
+
!الملف
|الغرض منه
+
!الغرض منه
 
|-
 
|-
 
|db/migrate/20140120201010_create_comments.rb
 
|db/migrate/20140120201010_create_comments.rb
|التهجير لإنشاء جدول التعليقات في قاعدة البيانات الخاصة بك (اسمك سيشمل بصمة وقت أخرى)
+
|التهجير لإنشاء جدول التعليقات في قاعدة البيانات الخاصة بك (اسم المعلِّق سيشمل بصمة وقت أخرى).
 
|-
 
|-
 
|app/models/comment.rb
 
|app/models/comment.rb
|نموذج التعليق Comment
+
|النمط <code>Comment</code>.
 
|-
 
|-
 
|test/models/comment_test.rb
 
|test/models/comment_test.rb
|اختبار التثبيت لنموذج التعليق
+
|عمليات الاختبار (Testing harness، أو يدعى أيضًا اطار الاختبار الآلي [automated test framework]) للنمط <code>Comment</code>.
 
|-
 
|-
 
|test/fixtures/comments.yml
 
|test/fixtures/comments.yml
|عينة التعليقات لاستخدامها للاختبار
+
|عينة التعليقات لاستخدامها في الاختبار.
 
|}
 
|}
أولًا، ألقى نظرة على app/models/comment.rb:
+
أولًا، ألقى نظرة على app/models/comment.rb:<syntaxhighlight lang="rails">
{| class="wikitable"
+
class Comment < ApplicationRecord
|class Comment < ApplicationRecord
+
  belongs_to :article
 
 
 belongs_to :article
 
 
 
 
end
 
end
|}
+
</syntaxhighlight>هذا شبيه جدًا بالنمط <code>Article</code> الذي رأيته من قبل. الاختلاف يكمن في السطر <code>belongs_to :article</code>، الذي يُهيئ ارتباط [[Rails/active record|Active Record]]. ستتعلم قليلًا عن الارتباطات في القسم التالي من هذا الدليل.
هذا شبيه جدًا بنموذج المقال Article الذي رأيته من قبل. الاختلاف في السطر belongs_to :article، الذي يُهيئ ارتباط للتسجيل الحي Active Record. ستتعلم قليلًا عن الارتباطات في القسم التالي من هذا الدليل.
 
  
الكلمة المفتاحية (reference:) تُستخدم في الأمر bash هو نوع خاص من البيانات للنماذج. تُنشيء عمود جديد في جدول قاعدة البيانات الخاص بك في اسم النموذج المُقدم مُلحق بها id_ الذي يأخذ قيم صحيحة. لفهم أفضل، حلل الملف db/schema.rb بعد تشغيل التهجير.
+
الكلمة المفتاحية (<code>reference:</code>) تُستخدم في الأمر [[Bash|bash]] هو نوع خاص من البيانات للأنماط. تُنشئ عمودًا جديدًا في جدول قاعدة البيانات الخاص بك مع اسم النمط (model) المعطى مضافًا إليها <code>id_</code> الذي يأخذ قيمًا صحيحةً. لفهم أفضل، حلل الملف db/schema.rb بعد تشغيل [[Rails/active record migrations|التهجير]].
 
 
بالإضافة إلى النموذج، يقوم ريلز بتهجير أيضًا ليُنشيء جدول قاعدة البيانات المناظر.
 
{| class="wikitable"
 
|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
 
  
 +
بالإضافة إلى النمط، يقوم ريلز [[Rails/active record migrations|بتهجير]] أيضًا لإنشاء جدول قاعدة البيانات المقابل:<syntaxhighlight lang="rails">
 +
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
 
end
|}
+
</syntaxhighlight>يُنشئ السطر <code>t.references</code> عمودًا لقيم عددية صحيحة يُسمى <code>article_id</code>، وفهرس له وقيد رئيسي خارجي يُشير إلى العمود <code>id</code> للجدول <code>articles</code>. اذهب ونفذ التهجير الآن:<syntaxhighlight lang="shell">
يُنشيء سطر t.references عمود صحيح يُسمى article_id، وفهرس له وقيد رئيسي خارجي يُشير إلى العمود id للجدول articles.
+
$ bin/rails db:migrate
 
+
</syntaxhighlight>إن ريلز ذكي كفاية لينفذ فقط التهجيرات التي لم تُنفَّذ بالفعل لقاعدة البيانات الحالية؛ في هذه الحالة سترى فقط:<syntaxhighlight lang="text">
اذهب ونفذ التهجير:
+
== CreateComments: migrating =================================================
{| class="wikitable"
 
|$ bin/rails db:migrate
 
|}
 
إن ريلز ذكي كفاية لينفذ فقط التهجيرات التي لم تُنفذ بالفعل لقاعدة البيانات الحالية، في هذه الحالة سترى فقط:
 
{| class="wikitable"
 
|==  CreateComments: migrating =================================================
 
 
 
 
-- create_table(:comments)
 
-- create_table(:comments)
 +
  -> 0.0115s
 +
==  CreateComments: migrated (0.0119s) ========================================
 +
</syntaxhighlight>
  
  -> 0.0115s
+
=== ربط الأنماط ===
 
+
ارتباطات [[Rails/active record|Active Record]] تسمح لك بالتصريح بسهولة عن العلاقة بين نمطين (models). في حالة التعليقات والمقالات، يمكنك أن تكتب العلاقات بهذه الطريقة:
<nowiki>==  CreateComments: migrated (0.0119s) ========================================</nowiki>
 
|}
 
 
 
=== ربط النماذج ===
 
ارتباطات التسجيل الحي Active Record تسمح لك بالإعلان بسهولة عن العلاقة بين نموذجين. في حالة التعليقات والمقالات، يمكنك أن تكتب العلاقات بهذه الطريقة:
 
 
* كل تعليق ينتمي إلى مقال واحد
 
* كل تعليق ينتمي إلى مقال واحد
 
* قد يحتوي المقال الواحد على الكثير من التعليقات
 
* قد يحتوي المقال الواحد على الكثير من التعليقات
في الحقيقة، هذا قريب جدًا من قواعد الصياغة التى يستخدمها ريلز للإعلان عن هذا الارتباط. لقد رأيت بالفعل سطر الترميز داخل نموذج التعليق comment وهو (app/models/comment.rb) الذي يجعل كل تعليق ينتمي إلى مقال:
+
في الحقيقة، هذا قريب جدًا من قواعد الصياغة التى يستخدمها ريلز للتصريح عن هذا الارتباط. لقد رأيت بالفعل سطرًا من الشيفرة داخل النمط <code>Comment</code>  (وهو app/models/comment.rb) الذي يجعل كل تعليق ينتمي إلى مقال:<syntaxhighlight lang="rails">
{| class="wikitable"
+
class Comment < ApplicationRecord
|class Comment < ApplicationRecord
+
  belongs_to :article
 
 
 belongs_to :article
 
 
 
 
end
 
end
|}
+
</syntaxhighlight>ستحتاج إلى تعديل app/models/article.rb لإضافة الجانب الأخر من الارتباط:<syntaxhighlight lang="rails">
ستحتاج إلى تعديل app/models/article.rb لإضافة الجانب الأخر من الارتباط:
+
class Article < ApplicationRecord
{| class="wikitable"
+
  has_many :comments
|class Article < ApplicationRecord
+
  validates :title, presence: true,
 
+
                    length: { minimum: 5 }
 has_many :comments
 
 
 
 validates :title, presence: true,
 
 
 
                   length: { minimum: 5 }
 
 
 
 
end
 
end
|}
+
</syntaxhighlight>هذان التصريحان يفعِّلان سلوكًا آليًّا يعمل إلى حد جيد. على سبيل المثال، إذا كنت تمتلك متغير النسخة <code>artilce@</code> يحوي مقالةً، فيُمكنك استرداد جميع التعليقات المنتمية إلى ذلك المقال في مصفوفة باستخدام <code>article.comments@</code>.
هذان الإعلانان يُمكنان سلوك أوتوماتيكي إلى حد جيد. على سبيل المثال، إذا كنت تمتلك مُتغير لحظى artilce@، يُمكنك استرداد جميع التعليقات المنتمية إلى ذلك المقال كمصفوفة باستخدام article.comments@.
 
{| class="wikitable"
 
|تنبيه: لمعلومات أكثر عن ارتباطات التسجيل الحي Active Record، ألقى نظرة على دليل ارتباطات التسجيل الحي Active Record.
 
|}
 
 
 
=== إضافة مسار للتعليقات ===
 
كما في وحدة التحكم Welcome، علينا أن نُضيف مسار ليعرف ريلز أين تُريد أن تنتقل لترى التعليقات. افتح ملف config/routes.rb ثانيةً وعدله كالتالي:
 
{| class="wikitable"
 
|resources :articles do
 
 
 
 resources :comments
 
  
 +
'''تنبيه''': لمعلومات أكثر عن ارتباطات [[Rails/active record|Active Record]]، ألقِ نظرة على دليل ارتباطات [[Rails/active record|Active Record]].
 +
=== إضافة مسار توجيه للتعليقات ===
 +
كما في وحدة التحكم <code>Welcome</code>، علينا أن نُضيف مسار ليعرف ريلز أين تُريد أن تنتقل لترى التعليقات. افتح الملف config/routes.rb ثانيةً وعدله بالشكل التالي:<syntaxhighlight lang="rails">
 +
resources :articles do
 +
  resources :comments
 
end
 
end
|}
+
</syntaxhighlight>يُنشيء هذا المورد <code>comments</code> كمورد مُتشعب ضمن <code>articles</code>. هذا جزء آخر للإمساك بالعلاقة الهرمية الموجودة بين المقالات والتعليقات.
يُنشيء هذا تعليقات comments كمورد مُتشعب خلال المقالات articles. هذا جزء أخر للإمساك بالعلاقة الهرمية الموجودة بين المقالات والتعليقات.
 
{| class="wikitable"
 
|تنبيه: لمعلومات أكثر عن التوجيه، ألقى نظرة على دليل توجيه ريلز.
 
|}
 
  
 +
'''تنبيه''': لمعلومات أكثر عن التوجيه (routing)، ألقِ نظرة على دليل [[Rails/routing|التوجيه من الخارج والداخل]] في ريلز.
 
=== توليد وحدة تحكم ===
 
=== توليد وحدة تحكم ===
بالنموذج الذي في المتناول، تحول انتباهك لإنشاء وحدة تحكم متطابقة. مرة أخرى سنستخدم نفس المولد الذي استخدمناه من قبل.
+
في النمط الذي بين أيدينا، يمكنك الانتقال إلى لإنشاء وحدة تحكم متطابقة. مرة أخرى سنستخدم نفس المولد الذي استخدمناه من قبل:<syntaxhighlight lang="shell">
{| class="wikitable"
+
$ bin/rails generate controller Comments
|$ bin/rails generate controller Comments
+
</syntaxhighlight>هذا يُنشيء خمسة ملفات ومجلد فارغ:
|}
 
هذا يُنشيء 5 ملفات ودليل فارغ:
 
 
{| class="wikitable"
 
{| class="wikitable"
|الملف/الدليل
+
!الملف/الدليل
|الغرض منه
+
!الغرض منه
 
|-
 
|-
 
|app/controllers/comments_controller.rb
 
|app/controllers/comments_controller.rb
|وحدة التحكم للتعليقات
+
|وحدة التحكم <code>Comments</code>.
 
|-
 
|-
 
|app/views/comments/
 
|app/views/comments/
|واجهات وحدة التحكم تُخزن هنا
+
|واجهات وحدة التحكم تُخزن هنا.
 
|-
 
|-
 
|test/controllers/comments_controller_test.rb
 
|test/controllers/comments_controller_test.rb
|اختبار وحدة التحكم
+
|اختبار وحدة التحكم.
 
|-
 
|-
 
|app/helpers/comments_helper.rb
 
|app/helpers/comments_helper.rb
|ملف مساعد الواجهة
+
|ملف مساعد الواجهة.
 
|-
 
|-
 
|app/assets/javascripts/comments.coffee
 
|app/assets/javascripts/comments.coffee
|لغة CoffeeScript لوحدة التحكم
+
|لغة CoffeeScript لوحدة التحكم.
 
|-
 
|-
 
|app/assets/stylesheets/comments.scss
 
|app/assets/stylesheets/comments.scss
|ورقة النمط المتعاقب لوحدة التحكم
+
|ملف [[CSS]] لوحدة التحكم.
 
|}
 
|}
كما في أي مدونة، سيكتب قرائنا تعليقاتهم مباشرة بعد قراءة المقال، وبعد إضافة تعليقهم، سيُرسلون إلى صفحة إظهار المقال ليروا تعليقهم مُدرج. لهذا، تقدم وحدة تحكم التعليقات CommentsController تابع لإنشاء تعليقات ومسح التعليقات المُزعجة عندما يصلون.
+
كما في أي مدونة، سيكتب قرائنا تعليقاتهم مباشرة بعد قراءة المقال؛ وبعد إضافة تعليقهم، سيُرسلون إلى صفحة إظهار المقال ليروا تعليقهم مُدرج. لهذا، توفر وحدة تحكم التعليقات <code>CommentsController</code> تابعًا لإنشاء تعليقات ومسح تلك المُزعجة عندما يفتحون المقال.
 
 
لذا أولًا، سنفعل قالب إظهار المقال (app/views/articles/show.html.erb) ليسمح لنا بعمل تعليق جديد.
 
{| class="wikitable"
 
|<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Title:</strong></nowiki>
 
 
 
 <%= @article.title %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Text:</strong></nowiki>
 
 
 
 <%= @article.text %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><h2>Add a comment:</h2></nowiki>
 
  
 +
لذا أولًا، سنفعل قالب إظهار المقال (الملف app/views/articles/show.html.erb) ليسمح لنا بعمل تعليق جديد:<syntaxhighlight lang="html">
 +
<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| %>
 
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <%= form.label :commenter %><br>
 
+
    <%= form.text_field :commenter %>
   <%= form.label :commenter %><nowiki><br></nowiki>
+
  </p>
 
+
  <p>
   <%= form.text_field :commenter %>
+
    <%= form.label :body %><br>
 
+
    <%= form.text_area :body %>
 <nowiki></p></nowiki>
+
  </p>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <%= form.submit %>
 
+
  </p>
   <%= form.label :body %><nowiki><br></nowiki>
 
 
 
   <%= form.text_area :body %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 <nowiki><p></nowiki>
 
 
 
   <%= form.submit %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 
<% end %>
 
<% end %>
 
+
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
 
<%= link_to 'Back', articles_path %>
 
<%= link_to 'Back', articles_path %>
|}
+
</syntaxhighlight>سيضيف هذا نموذجًا إلى صفحة إظهار المقال التي تُنشئ تعليقًا جديدًا باستدعاء الإجراء <code>create</code> الخاص بوحدة التحكم <code>CommentsController</code>. الاستدعاء <code>form_with</code> هنا يستخدم مصفوفةً تبني مسار توجيه مُتشعب مثل articles/1/comments/.
سيضيف هذا نموذج إلى صفحة إظهار المقال التي تُنشيء تعليق جديد باستدعاء الإجراء إنشاء create الخاص بـ وحدة تحكم التعليقات CommentsController. الاستدعاء form_with هنا يستخدم مصفوفة، التي ستبني مسار مُشعب مثل articles/1/comments/.
 
 
 
دعنا نشغل الإجراء إنشاء في app/controllers/comments_controller.rb:
 
{| class="wikitable"
 
|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
 
  
 +
دعنا نشغل الإجراء <code>create</code> في الملف app/controllers/comments_controller.rb:<syntaxhighlight lang="rails">
 +
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
 
end
|}
+
</syntaxhighlight>سترى بعض التعقيد هنا عما فعلناه سابقًا في وحدة التحكم الخاصة بالمقالات. هذا تأثير التشعب الذي أعددته آنفًا. كل طلب لتعليق عليه تتبع المقال الذي يرتبط به هذا التعليق، وهكذا يجلب الاستدعاء الابتدائي للتابع <code>find</code> للنمط <code>Article</code> المقال المقصود.
سترى تعقيد أكثر بقليل هنا عما فعلته في وحدة التحكم الخاصة بالمقالات. هذا تأثير التشعب الذي أعدته. كل طلب لتعليق عليه تتبع المقال الذي يرتبط به التعليق، ومن ثم الاستدعاء الأولى للتابع find لنموذج المقال Article للحصول على المقال المقصود.
 
  
بالإضافة إلى ذلك، يأخذ الترميز ميزة من التوابع المتاحة للارتباط. نستخدم التابع إنشاء create في article.comments@ لإنشاء وحفظ التعليق. هذا سيربط التعليق تلقائيًا لينتمي إلى المقال المعين.
+
بالإضافة إلى ذلك، تأخذ الشيفرة بعضًا من التوابع المتاحة للارتباط. يمكننا استعمال التابع <code>create</code> في <code>article.comments@</code> لإنشاء وحفظ التعليق. هذا سيربط التعليق تلقائيًا لينتمي إلى المقال المعين.
 
 
عند القيام بالتعليق الجديد، نُرجع المُستخدم مرة أخرى للمقال الأصلي باستخدام المساعد (article_path(@article. كما رأينا بالفعل، هذا سيستدعي الإجراء إظهار show من وحدة تحكم المقال ArticleController وسيصدر بدوره القالب show.html.erb. هذا حيث نُريد إظهار التعليق، لذا لنضيف ذلك إلى app/views/articles/show.html.erb.
 
{| class="wikitable"
 
|<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Title:</strong></nowiki>
 
 
 
 <%= @article.title %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Text:</strong></nowiki>
 
 
 
 <%= @article.text %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><h2>Comments</h2></nowiki>
 
  
 +
عند إنشاء تعليق جديد، نرسل المستخدم مجدَّدًا للمقال الأصلي باستخدام المساعد <code>(article_path(@article</code>. كما رأينا بالفعل، هذا سيستدعي الإجراء <code>show</code> من وحدة التحكم <code>ArticleController</code> الذي سيصيِّر بدوره القالب show.html.erb. هذا حيث نُريد إظهار التعليق، لذا لنُضِف ذلك إلى الملف app/views/articles/show.html.erb:<syntaxhighlight lang="html">
 +
<p>
 +
  <strong>Title:</strong>
 +
  <%= @article.title %>
 +
</p>
 +
 +
<p>
 +
  <strong>Text:</strong>
 +
  <%= @article.text %>
 +
</p>
 +
 +
<h2>Comments</h2>
 
<% @article.comments.each do |comment| %>
 
<% @article.comments.each do |comment| %>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <strong>Commenter:</strong>
 
+
    <%= comment.commenter %>
   <nowiki><strong>Commenter:</strong></nowiki>
+
  </p>
 
+
   <%= comment.commenter %>
+
  <p>
 
+
    <strong>Comment:</strong>
 <nowiki></p></nowiki>
+
    <%= comment.body %>
 
+
  </p>
 <nowiki><p></nowiki>
 
 
 
   <nowiki><strong>Comment:</strong></nowiki>
 
 
 
   <%= comment.body %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 
<% end %>
 
<% end %>
 
+
<nowiki><h2>Add a comment:</h2></nowiki>
+
<h2>Add a comment:</h2>
 
 
 
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
 
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <%= form.label :commenter %><br>
 
+
    <%= form.text_field :commenter %>
   <%= form.label :commenter %><nowiki><br></nowiki>
+
  </p>
 
+
  <p>
   <%= form.text_field :commenter %>
+
    <%= form.label :body %><br>
 
+
    <%= form.text_area :body %>
 <nowiki></p></nowiki>
+
  </p>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <%= form.submit %>
 
+
  </p>
   <%= form.label :body %><nowiki><br></nowiki>
 
 
 
   <%= form.text_area :body %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 <nowiki><p></nowiki>
 
 
 
   <%= form.submit %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 
<% end %>
 
<% end %>
 
+
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 +
<%= link_to 'Back', articles_path %>
  
<%= link_to 'Back', articles_path %>
+
</syntaxhighlight>الآن، يمكنك أن تُضيف مقالات وتعليقات إلى مدونتك بحيث يظهرون في المواضع الصحيحة.
|}
 
الآن، تُضيف مقالات وتعليقات إلى مدونتك بحيث يظهرون في المواضع الصحيحة.
 
  
 +
[[ملف:article_with_comments.png|بديل=ظهور قسم أسفل المقال لإضافة تعليقات إليه.|بدون|تصغير|500بك|ظهور قسم أسفل المقال لإضافة تعليقات إليه.]]
 
== إعادة هيكلة ==
 
== إعادة هيكلة ==
الآن، لديك مقالات وتعليقات تعمل، ألقى نظرة على القالب app/views/articles/show.html.erb. ستجده طويل وغير ملائم. يمكنك استخدام ملفات جزئية لمعالجة ذلك.
+
الآن، بما أنَّه لدينا مقالات وتعليقات تعمل بشكل صحيح، لنلقِ نظرة على القالب app/views/articles/show.html.erb؛ ستجده طويلًأ وغير ملائم. يمكنك استخدام ملفات جزئية لمعالجة ذلك.
  
=== تصدير المجموعات الجزئية ===
+
=== تصيير المجموعات الجزئية ===
بدايةً، سننشئ ملف تعليق جزئي لإظهار جميع التعليقات للمقال. أنشئ الملف app/views/comments/_comment.html.erb وضع التالي به:
+
بدايةً، سننشئ ملف تعليق جزئي لإظهار جميع التعليقات للمقال. أنشئ الملف app/views/comments/_comment.html.erb وضع التالي به:<syntaxhighlight lang="html">
{| class="wikitable"
+
<p>
|<nowiki><p></nowiki>
+
  <strong>Commenter:</strong>
 
+
  <%= comment.commenter %>
 <nowiki><strong>Commenter:</strong></nowiki>
+
</p>
 
+
 <%= comment.commenter %>
+
<p>
 
+
  <strong>Comment:</strong>
<nowiki></p></nowiki>
+
  <%= comment.body %>
 
+
</p>
<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Comment:</strong></nowiki>
 
 
 
 <%= comment.body %>
 
 
 
<nowiki></p></nowiki>
 
|}
 
ومن ثم تُغير app/views/articles/show.html.erb لتبدو كالتالي:
 
{| class="wikitable"
 
|<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Title:</strong></nowiki>
 
 
 
 <%= @article.title %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><p></nowiki>
 
 
 
 <nowiki><strong>Text:</strong></nowiki>
 
 
 
 <%= @article.text %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><h2>Comments</h2></nowiki>
 
  
 +
</syntaxhighlight>ومن ثم عدل الملف app/views/articles/show.html.erb ليبدو بالشكل التالي:<syntaxhighlight lang="html">
 +
<p>
 +
  <strong>Title:</strong>
 +
  <%= @article.title %>
 +
</p>
 +
 +
<p>
 +
  <strong>Text:</strong>
 +
  <%= @article.text %>
 +
</p>
 +
 +
<h2>Comments</h2>
 
<%= render @article.comments %>
 
<%= render @article.comments %>
 
+
<nowiki><h2>Add a comment:</h2></nowiki>
+
<h2>Add a comment:</h2>
 
 
 
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
 
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <%= form.label :commenter %><br>
 
+
    <%= form.text_field :commenter %>
   <%= form.label :commenter %><nowiki><br></nowiki>
+
  </p>
 
+
  <p>
   <%= form.text_field :commenter %>
+
    <%= form.label :body %><br>
 
+
    <%= form.text_area :body %>
 <nowiki></p></nowiki>
+
  </p>
 
+
  <p>
 <nowiki><p></nowiki>
+
    <%= form.submit %>
 
+
  </p>
   <%= form.label :body %><nowiki><br></nowiki>
 
 
 
   <%= form.text_area :body %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 <nowiki><p></nowiki>
 
 
 
   <%= form.submit %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 
<% end %>
 
<% end %>
 
+
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
 
<%= link_to 'Back', articles_path %>
 
<%= link_to 'Back', articles_path %>
|}
+
</syntaxhighlight>الآن، هذا سيصيِّر الملف الجزئي app/views/comments/_comment.html.erb مرةً لكل تعليق في المجموعة <code>article.comments@</code>. بما أنَّ التابع <code>render</code> يتكرر خلال المجموعة <code>article.comments@</code>، يخصص كل تعليق لمتغير محلي له نفس اسم الملف الجزئي؛ في هذه الحالة، سيكون التابع <code>comment</code> متاح لنا في الملف الجزئي لنظهره.
الآن، هذا سيصدر الملف الجزئي app/views/comments/_comment.html.erb مرة لكل تعليق في المجموعة article.comments@. لأن التابع render يتكرر خلال المجموعة article.comments@، يخصص كل تعليق لمتغير محلى له نفس اسم الملف الجزئي، في هذه الحالة، سيكون التابع تعليق comment وهو متاح لنا في الملف الجزئي لنظهره.
+
=== تصيير نموذج ملف جزئي ===
 
+
دعنا ننقل قسم التعليقات الجديد لخارج ملفه الجزئي. مرة آخرى، أنشئ ملفًا جديدًا يدعى app/views/comments/_form.html.erb يحتوي على:<syntaxhighlight lang="html">
=== تصدير نموذج الملف الجزئي ===
+
<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
دعنا ننقل قسم التعليقات الجديد لذلك للخارج لملفه الجزئي. مرة آخرى، تُنشيء ملف app/views/comments/_form.html.erb يحتوي على:
+
  <p>
{| class="wikitable"
+
    <%= form.label :commenter %><br>
|<nowiki><%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %></nowiki>
+
    <%= form.text_field :commenter %>
 
+
  </p>
 <nowiki><p></nowiki>
+
  <p>
 
+
    <%= form.label :body %><br>
   <%= form.label :commenter %><nowiki><br></nowiki>
+
    <%= form.text_area :body %>
 
+
  </p>
   <%= form.text_field :commenter %>
+
  <p>
 
+
    <%= form.submit %>
 <nowiki></p></nowiki>
+
  </p>
 
 
 <nowiki><p></nowiki>
 
 
 
   <%= form.label :body %><nowiki><br></nowiki>
 
 
 
   <%= form.text_area :body %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 <nowiki><p></nowiki>
 
 
 
   <%= form.submit %>
 
 
 
 <nowiki></p></nowiki>
 
 
 
 
<% end %>
 
<% end %>
|}
+
</syntaxhighlight>وثم عدل الملف app/views/articles/show.html.erb لتبدو كالتالي:<syntaxhighlight lang="html">
وثم تجعل app/views/articles/show.html.erb تبدو كالتالي:
+
<p>
{| class="wikitable"
+
  <strong>Title:</strong>
|<nowiki><p></nowiki>
+
  <%= @article.title %>
 
+
</p>
 <nowiki><strong>Title:</strong></nowiki>
+
 
+
<p>
 <%= @article.title %>
+
  <strong>Text:</strong>
 
+
  <%= @article.text %>
<nowiki></p></nowiki>
+
</p>
 
+
<nowiki><p></nowiki>
+
<h2>Comments</h2>
 
 
 <nowiki><strong>Text:</strong></nowiki>
 
 
 
 <%= @article.text %>
 
 
 
<nowiki></p></nowiki>
 
 
 
<nowiki><h2>Comments</h2></nowiki>
 
 
 
 
<%= render @article.comments %>
 
<%= render @article.comments %>
 
+
<nowiki><h2>Add a comment:</h2></nowiki>
+
<h2>Add a comment:</h2>
 
 
 
<%= render 'comments/form' %>
 
<%= render 'comments/form' %>
 
+
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
<%= link_to 'Edit', edit_article_path(@article) %> |
 
 
<%= link_to 'Back', articles_path %>
 
<%= link_to 'Back', articles_path %>
|}
+
</syntaxhighlight>تُعرِّف الكلمة render الثانية قالب الملف الجزئي الذي نُريد تصييره (render)، الذي هو comments/form. إن ريلز ذكي كفايةً ليضع الخط المائل في تلك السلسلة النصية ويلاحظ أنك تريد تصيير الملف form.html.erb_ في المجلد app/views/comments.
كلمة render الثانية تُعرف قالب الملف الجزئي الذي نُريد تصديره، comments/form. إن ريلز ذكي كفايةً ليضع السلاش الأمامية في تلك السلسلة النصية ويلاحظ أنك تريد تصدير ملف form.html.erb_ في الدليل app/views/comments.
 
  
الكائن article@ متاح لأي ملف جزئي صدر في الواجهة لأننا عرفناه كمتغير لحظى.
+
الكائن <code>article@</code> متاح لأي ملف جزئي صُيِّر (rendered) في الواجهة لأننا عرفناه كمتغير نسخة.
  
 
== مسح التعليقات ==
 
== مسح التعليقات ==
ميزة هامة أخرى للمدونة هي إمكانية مسح التعليقات المُزعجة. لعمل ذلك، علينا تنفيذ رابط من نوع ما في الواجهة وإجراء تدمير destroy في وحدة تحكم التعليقات CommentsController.
+
ميزة هامة أخرى للمدونة هي إمكانية مسح التعليقات المُزعجة. لعمل ذلك، علينا تنفيذ رابط من نوع ما في الواجهة والإجراء <code>destroy</code> في وحدة التحكم <code>CommentsController</code>.
  
بدايةً، دعنا نضع رابط المسح في الملف الجزئي app/views/comments/_comment.html.erb.
+
بدايةً، دعنا نضع رابط المسح في الملف الجزئي app/views/comments/_comment.html.erb:<syntaxhighlight lang="html">
{| class="wikitable"
+
<p>
|<nowiki><p></nowiki>
+
  <strong>Commenter:</strong>
 
+
  <%= comment.commenter %>
 <nowiki><strong>Commenter:</strong></nowiki>
+
</p>
 
+
 <%= comment.commenter %>
+
<p>
 
+
  <strong>Comment:</strong>
<nowiki></p></nowiki>
+
  <%= comment.body %>
 
+
</p>
<nowiki><p></nowiki>
+
 
+
<p>
 <nowiki><strong>Comment:</strong></nowiki>
+
  <%= link_to 'Destroy Comment', [comment.article, comment],
 
+
              method: :delete,
 <%= comment.body %>
+
              data: { confirm: 'Are you sure?' } %>
 
+
</p>
<nowiki></p></nowiki>
+
</syntaxhighlight>بالضغط على رابط مسح التعليق "Destroy Comment" الجديد هذا سيطلق <code>DELETE</code>
 
 
<nowiki><p></nowiki>
 
 
 
 <%= link_to 'Destroy Comment', [comment.article, comment],
 
 
 
              method: :delete,
 
 
 
              data: { confirm: 'Are you sure?' } %>
 
 
 
<nowiki></p></nowiki>
 
|}
 
بالضغط على رابط مسح التعليق "Destroy Comment" الجديد هذا سيطلق DELETE
 
 
 
/articles/:article_id/comments/:id لوحدة تحكم التعليقات CommentsController الخاص بنا، ويُستخدم هذا لإيجاد التعليق الذي نود مسحه، دعنا نضيف إجراء تدمير destroy لوحدة التحكم الخاصة بنا(app/controllers/comments_controller.rb):
 
{| class="wikitable"
 
|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
 
  
 +
<code>/articles/:article_id/comments/:id</code> لوحدة التحكم <code>CommentsController</code> الخاصة بنا، ويُستخدم هذا لإيجاد التعليق الذي نود مسحه؛ دعنا نضيف الإجراء <code>destroy</code> لوحدة التحكم الخاصة بنا (في الملف app/controllers/comments_controller.rb):<syntaxhighlight lang="rails">
 +
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
 
end
|}
+
</syntaxhighlight>الإجراء <code>destroy</code> سيجد المقال الذي نبحث عنه، ويحدد موضع التعليق داخل المجموعة <code>article.comments@</code>، ثم يمسحه من قاعدة البيانات ويرسلنا للإجراء <code>show</code> الخاص بالمقال.
الإجراء تدمير destroy سيجد المقال الذي نبحث عنه، ويحدد موضع التعليق داخل المجموعة article.comments@، ثم يمسحه من قاعدة البيانات ويرسلنا للإجراء إظهار show الخاص بالمقال.
 
  
 
=== مسح الكائنات المصاحبة ===
 
=== مسح الكائنات المصاحبة ===
إذا مسحت مقال، فإن تعليقاته المصاحبة بحاجة للمسح أيضًا، وإلا سيشغلون مساحة في قاعدة البيانات. يسمح ريلز لك باستخدام الاختيار dependent ويعني "التابع" لارتباط ما لتحقيق ذلك. عدل نموذج المقال Article app/models/article.rb، كالتالي:
+
إذا مسحت مقال، فإن تعليقاته المرتبطة به بحاجة للمسح أيضًا، وإلا سيشغلون مساحة في قاعدة البيانات. يسمح ريلز لك باستخدام الخيار <code>dependent</code> ويعني "التابع" لارتباط ما لتحقيق ذلك. عدل النمط المقال <code>model</code> في الملف Article app/models/article.rbmodel كالتالي:<syntaxhighlight lang="rails">
{| class="wikitable"
+
class Article < ApplicationRecord
|class Article < ApplicationRecord
+
  has_many :comments, dependent: :destroy
 
+
  validates :title, presence: true,
 has_many :comments, dependent: :destroy
+
                    length: { minimum: 5 }
 
 
 validates :title, presence: true,
 
 
 
                   length: { minimum: 5 }
 
 
 
 
end
 
end
|}
+
</syntaxhighlight>
  
 
== الأمان ==
 
== الأمان ==
  
 
=== الاستيثاق الأساسي ===
 
=== الاستيثاق الأساسي ===
إذا كنت ستنشر مدونتك على الإنترنت، باستطاعة أي أحد أن يضيف ويعدل ويمسح مقالات ويمسح تعليقات.
+
إذا كنت ستنشر مدونتك على الإنترنت، فسيكون باستطاعة أي أحد أن يضيف ويعدل ويمسح المقالات والتعليقات أيضًا.
  
يُقدم ريلز نظام بسيط جدًا للاستيثاق HTTP والذي سيعمل جيدًا في هذا الموقف.
+
يُقدم ريلز نظامًا بسيطًا جدًا للاستيثاق HTTP والذي سيعمل جيدًا في هذا الموقف.
  
في وحدة تحكم المقالات ArticlesController علينا أن نملك طريقة لمنع الوصول للإجراءات المتعددة إذا لم يكن الشخص موثوق به. نستخدم التابع http_basic_authenticate_with في ريلز، والذي يسمح بالوصول للإجراء المطلوب إذا سمح هذا التابع له.
+
في وحدة التحكم <code>ArticlesController</code>، علينا أن نملك طريقةً لمنع الوصول للإجراءات المتعددة إذا لم يكن الشخص موثوقًا به. نستخدم التابع <code>http_basic_authenticate_with</code> في ريلز، والذي يسمح بالوصول للإجراء المطلوب إذا سمح هذا التابع له.
  
لاستخدام نظام الاستيثاق، نحدد ذلك في أعلى وحدة تحكم المقالات ArticlesController في app/controllers/articles_controller.rb. في حالتنا، نُريد المستخدم أن يكون موثوقًا لكل إجراء ماعدا الإجراء فهرس index والإجراء إظهار show، لذا نكتب ذلك:
+
لاستخدام نظام الاستيثاق، نحدِّد ذلك في أعلى وحدة تحكم المقالات <code>ArticlesController</code> في app/controllers/articles_controller.rb. في حالتنا، نُريد من المستخدم أن يكون موثوقًا لكل إجراء باستثناء الإجراء <code>index</code> والإجراء <code>show</code>، لذا نضيف ذلك عبر الشيفرة التالية:<syntaxhighlight lang="rails">
{| class="wikitable"
+
class ArticlesController < ApplicationController
|class ArticlesController < ApplicationController
+
 
+
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
<nowiki> http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]</nowiki>
+
 
+
  def index
 def index
+
    @articles = Article.all
 
+
  end
   @articles = Article.all
+
 +
  # snippet for brevity
 +
</syntaxhighlight>نُريد أن نسمح للمستخدمين الموثوقين فقط بمسح التعليقات، لذا في وحدة تحكم التعليقات CommentsController (الملف app/controllers/comments_controller.rb) ونكتب:<syntaxhighlight lang="rails">
 +
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
 +
</syntaxhighlight>الآن، إذا حاولت إنشاء مقال جديد، سيُرحب بك تحدي الاستيثاق بنظام HTTP الأساسي:
  
 end
+
[[ملف:challenge.png|بديل=إضافة تحدي استيثاق HTTP الأساسي لمنع أي أحد غير موثوق من التعديل على التعديل على المقالات وحذفها. |بدون|تصغير|500بك|إضافة تحدي استيثاق HTTP الأساسي لمنع أي أحد غير موثوق من التعديل على التعديل على المقالات وحذفها. ]]
  
 # snippet for brevity
+
هناك توابع استيثاق أخرى متاحة لتطبيقات ريلز. إضافتان مشهورتان للاستيثاق لريلز هما [https://github.com/plataformatec/devise Devise] و [https://github.com/binarylogic/authlogic Authlogic] بالإضافة لعدد من الإضافات الأخرى.
|}
 
نُريد أن نسمح للمستخدمين الموثوقين فقط بمسح التعليقات، لذا في وحدة تحكم التعليقات CommentsController (app/controllers/comments_controller.rb)  ونكتب:
 
{| class="wikitable"
 
|class CommentsController < ApplicationController
 
  
 http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
+
=== اعتبارات أمان الأخرى ===
 
+
الأمان، خصوصًا في تطبيقات الويب، منطقةٌ واسعةٌ ومُفصلةٌ. الأمان في تطبيق ريلز الخاص بك مُغطى بعمق أكثر في دليل [[Rails/security|تأمين تطبيقات ريلز.]]
 def create
 
 
 
   @article = Article.find(params[:article_id])
 
 
 
   # ...
 
 
 
 end
 
 
 
 # snippet for brevity
 
|}
 
الآن إذا حاولت إنشاء مقال جديد، سيُرحب بك تحدي للاستيثاق بنظام HTTP الأساسي:
 
 
 
هناك توابع استيثاق أخرى متاحة لتطبيقات ريلز. إضافتان مشهورتان للاستيثاق لـ ريلز هما Devise rails engine و Authlogic gen بالإضافة لعدد من الإضافات الأخرى.
 
 
 
=== اعتبارات الأمان الأخرى ===
 
الأمان، خصوصًا في تطبيقات الويب، منطقة واسعة ومُفصلة. الأمان في تطبيق ريلز الخاص بك مُغطى بعمق أكثر في دليل الأمان في Ruby on ريلز
 
  
 
== ما هو التالي؟ ==
 
== ما هو التالي؟ ==
الآن رأينا تطبيق ريلز الخاص بك الأول، عليك أن تشعر بالحرية لتحديثه وتجربته بنفسك.
+
الآن، رأيت تطبيق ريلز الخاص بك الأول؛ لك الحرية المطلقة لتحديثه وتجربته بنفسك.
 
 
تذكر ليس عليك القيام بكل شيء بدون مساعدة. لأنك تحتاج إلى المساعدة في بدء وتشغيل ريلز، أشعر بالحرية لطلب الاستشارة من تلك الموارد الداعمة:
 
 
 
أدلة Ruby on ريلز
 
 
 
دورة Ruby on ريلز التعليمية
 
 
 
قائمة المراسلة الخاصة بـ Ruby on ريلز
 
 
 
قناة rubyonrails# على irc.freenode.net
 
 
 
== تهيئة Gotchas ==
 
أسهل طريقة للعمل على ريلز أن تُخزن كل الداتا الخارجية كـ UTF-8. إذا لم تفعل، مكتبات Ruby و ريلز ستكون قادرة على تحويل كل الداتا الأصلية لك إلى UTF8، ولكن هذا لا يعمل بشكل موثوق به، لذا من الأفضل أن تتأكد أن كل البيانات الخارجية UTF-8.
 
 
 
إذا أخطأت في تلك المنطقة، العَرَضْ الشائع ماسة سوداء بداخلها علامة استفهام تظهر في المُتصفح. عَرَضْ أخر، هي الرموز ك "¼A" تظهر بدلًا من "ü". يأخذ ريلز عدد من الخطوات الداخلية لمعالجة الأسباب الشائعة لتلك المشاكل التي يمكن اكتشافها وتصحيحها تلقائيًا. ولكن إذا كان لديك بيانات خارجية غير مخزنة كـ UTF-8، ربما تتسبب عرضيًا في تلك الأنواع من المشاكل التي لا تُكتشف تلقائيًا أو تُصحح بواسطة ريلز.
 
  
مصدران شائعان جدًا للبيانات التي ليست URF-8:
+
تذكر، ليس عليك القيام بكل شيء بدون مساعدة. لأنك تحتاج إلى المساعدة في بدء وتشغيل ريلز، أشعر بالحرية لطلب الاستشارة من تلك الموارد الداعمة:
 +
* [[Rails|الصفحة الرئيسية لتوثيق ريلز]]
  
محرر النص لديك: معظم محرري النصوص (مثل TextMate)، افتراضيًا يحفظون الملفات كـ UTF-8. إذا لم يكن محررك كذلك، سينتج عن ذلك رموز خاصة تُدخلها في قوالبك (مثل é) أن يظهر كماسة بداخلها علامة استفهام في المُتصفح. ينطبق هذا أيضًا على ملفات الترجمة i18n. معظم المحررين والتي بالفعل ليست افتراضية لـ UTF-8 (مثل بعض الإصدارات من Dreamweaver) تعرض طريقة لتغيير الافتراضي إلى UTF-8. افعل ذلك.
+
* [https://www.railstutorial.org/book دورة تعليمية حول ريلز.]
  
قاعدة بياناتك: اقتراضيًا يُحول ريلز البيانات من قاعدة بياناتك إلى UTF-8. ولكن  إذا لم تكن قاعدة بياناتك  تستخدم UTF-8 داخليًا، ربما لا تقدر على حفظ كل الرموز التي يُدخلها مستخدموك.  على سبيل المثال، إذا كانت قاعدة بياناتك تستخدم الرموز اللاتينية  Latin-1 داخليًا، ويُدخل مستخدموك رمز روسي أو عبري أو ياباني، ستضيع البيانات إلى الأبد بمجرد دخولها في قاعدة البيانات. إذا أمكن، استخدم UTF-8 للتخزين الداخلي لقاعدة البيانات.
+
* [http://groups.google.com/group/rubyonrails-talk قائمة المراسلة الخاصة بريلز.]
  
== ملاحظات المستخدمين ==
+
* قناة [irc://irc.freenode.net/#rubyonrails rubyonrails#] على irc.freenode.net.
نُشجعك أن تُساعد في تحسين جودة هذا الدليل.
+
* [https://academy.hsoub.com/learn/ruby-web-application-development/ دورة تطوير تطبيقات الويب باستخدام لغة روبي وإطار ريلز.]
  
رجاءً شارك في حال رأيت أي أخطاء كتابية أو تقنية. للبدء، أقرأ قسم مساهمات المستندات.
+
== التهيئات المطلوبة والمشكلات المحتملة ==
 +
أسهل طريقة للعمل مع ريلز هي أن تُخزن كل البيانات الخارجية بترميز UTF-8. إذا لم تفعل ذلك، مكتبات [[Ruby|روبي]] و<nowiki/>[[Rails|ريلز]] ستكون قادرةً على تحويل كل البيانات الأصلية لك إلى الترميز UTF-8، ولكن هذا لا يعمل بشكل موثوق، لذا من الأفضل أن تتأكد أن كل البيانات الخارجية مرمزة بالترميز UTF-8.
  
ربما تجد محتوى غير كامل أو مضمون غير حديث
+
إذا أخطأت في هذا الشأن، فالعَرَضْ الشائع هو ظهور ماسة سوداء (black diamond) بداخلها علامة استفهام تظهر في المُتصفح. عَرَضٌ أخر، هي ظهور رموز غير مفهومة مثل "ü"  تظهر بدلًا من "ü". يأخذ ريلز عدد من الخطوات الداخلية لمعالجة الأسباب الشائعة لتلك المشاكل التي يمكن اكتشافها وتصحيحها تلقائيًا. ولكن إذا كان لديك بيانات خارجية غير مخزنة بالترميز UTF-8، ربما تتسبب عرضيًا في ظهور تلك الأنواع من المشاكل التي لا تُكتشَف تلقائيًا أو تُصحَّح بواسطة ريلز.
  
رجاءً أضف أي مستندات مفقودة من المستند الرئيسي. تأكد من مراجعتك لأدلة Edge أولًا لتتأكد إذا ما كانت المشاكل قد حُلت بالفعل أو لا في فرع المستند الرئيسي. ألقي نظرة على القواعد الإرشادية لأدلة Ruby on ريلز للنمط والأعراف.
+
مصدران شائعان جدًا للبيانات التي ليست مرمزة بالترميز UTF-8:
 +
* محرر النصوص الذي تعمل عليه: معظم محرري النصوص (مثل TextMate)، افتراضيًا يحفظون الملفات بالترميز UTF-8. إذا لم يكن محررك كذلك، سينتج عن ذلك رموز خاصة تُدخلها في قوالبك (مثل é) وهو ما يؤدي إلى ظهور ماسة بداخلها علامة استفهام في المُتصفح. ينطبق هذا أيضًا على ملفات الترجمة [[Rails/i18n|i18n]]. معظم محررات النصوص التي لا تستعمل الترميز UTF-8 افتراضيًّا (مثل بعض الإصدارات من Dreamweaver) توفر طريقة لتغيير الترميز الافتراضي إلى UTF-8. ننصحك بشدة بفعل ذلك.
  
إذا لسبب ما وجدت خطأ ولكن لا تستطيع إصلاحه بنفسك، من فضلك أفتح مشكلة.
+
* قاعدة بياناتك: يُحول ريلز البيانات من قاعدة بياناتك إلى الترميز UTF-8 افتراضيًا. ولكن  إذا لم تكن قاعدة بياناتك  تستخدم الترميز UTF-8 داخليًا، ربما لا تقدر على حفظ كل الرموز التي يُدخلها مستخدموك. على سبيل المثال، إذا كانت قاعدة بياناتك تستخدم الرموز اللاتينية (الترميز Latin-1) داخليًا، ويُدخل مستخدموك رمز روسي أو عبري أو ياباني، فستضيع البيانات إلى الأبد بمجرد دخولها في قاعدة البيانات. إذا أمكن، استخدم الترميز UTF-8 للتخزين الداخلي لقاعدة البيانات.
  
وأخيرًا وليس أخرًا، لأي مناقشة عن مستندات Ruby on ريلز مرحب بك على قائمة مراسلة مستندات Ruby on ريلز
+
== مصادر ==
 +
* [https://guides.rubyonrails.org/getting_started.html صفحة Getting Started with Rails في توثيق Ruby On Rails الرسمي.]

المراجعة الحالية بتاريخ 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 للتخزين الداخلي لقاعدة البيانات.

مصادر