تعرّفنا في الدرس السابق بإيجاز على طريقة عمل إطار العمل Ruby on Rails حيث تعرّفنا على المتحكّمات والعروض وبدأنا بإنشاء تطبيق المدوّنة البسيطة وأنشأنا في الدرس السابق الاستمارة الخاصة بإضافة مقالة جديدة، ولكن وصلنا إلى النقطة التي نحتاج فيها إلى تخزين المقالة في قاعدة البيانات، وهنا يأتي دور النماذج Models.
تحمل النماذج في Rails اسمًا بصيغة المفرد في حين يحمل الجدول المرتبط بها في قاعدة البيانات اسمًا بصيغة الجمع. تتيح أداة المولّد generator في Rails إنشاء النماذج ويلجأ أغلب المطوّرين إلى هذه الأداة لإنشاء النماذج.
لإنشاء نموذج جديد استخدم الأمر التالي في سطر الأوامر:
$ bin/rails generate model Article title:string text:text
من خلال هذه الأمر نخبر Rails بأننا نرغب في إنشاء نموذج باسم Article
إلى جانب خاصّية title
من نوع string
، وخاصّية text
من نوع text
. تضاف هذه الخواص بصورة تلقائية إلى جدول المقالات في قاعدة البيانات ويتم ربطها بنموذج Article
.
ويستجيب Rails لهذا الأمر بإنشاء عدد من الملفات، وما يهمّنا منها الآن هما app/models/article.rb
و db/migrate/20140120191729_create_articles.rb
(لاحظ أن اسم الملف الثاني يختلف قليلًا عن هذا الاسم). الملف الثاني مسؤول عن إنشاء بنية قاعدة البيانات، وهو ما سنتحدث عنه بعد قليل.
Quoteملاحظة:
التسجيلة النشطة Active Reord ذكية بما فيه الكفاية لتربط اسم العمود بخاصية النموذج المقابلة، وهذا يعني أنّك لست بحاجة إلى الإعلان عن الخصائص داخل نموذج Rails، إذ ستقوم التسجيلة النشطة بذلك نيابة عنك.
إجراء عملية التهجير Migration
كما لاحظنا فإن الأمر bin/rails generate model
قد أنشأ ملف تهجير لقاعدة البيانات داخل المجلد db/migrate
. والتهجيرات هي عبارة عن أصناف مصمّمة لتسهيل عملية إنشاء الجداول في قواعد البيانات والتعديل عليها. يستخدم Rails أوامر rake
لإجراء التهجيرات، ويمكن التراجع عن عملية التجهير بعد إجرائها على قاعدة البيانات. تتضمّن أسماء ملفات التهجير ختمًا زمنيًا لضمان معالجة هذه الملفات حسب التسلسل الزمني لإنشائها.
لو ألقينا نظرة في ملف 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` والذي يتم استدعاؤه عند إجراء عملية التهجير. حتى الحدث المُعرّف ضمن التابع قابل للتراجع، ما يعني أن Rails قادر على التراجع عن التغييرات الحاصلة من إجراء عملية التهجير في حال أردت ذلك في وقت لاحق. عند إجراء عملية التهجير هذه سيتم إنشاء جدول باسم `articles` يتضمن عمودًا من نوع `string` وآخر من نوع `text`، إضافة إلى عمودين للختم الزمني يمكن لـ Rails من خلالهما متابعة تواريخ إنشاء وتعديل المقالات. لتنفيذ عمية التهجير توجّه إلى سطر الأوامر ونفذ الأمر التالي:
$ bin/rails db:migrate
سينفّذ Rails أمر التهجير التالي وسيخبرك بإنشاء جدول Articles.
== CreateArticles: migrating ==================================================
-- create_table(:articles)
-> 0.0019s
== CreateArticles: migrated (0.0020s) =========================================
Quoteملاحظة:
نظرًا لكوننا نعمل في بيئة التطوير Development Environment بصورة افتراضية، سيتم تنفيذ الأمر السابق على قاعدة البيانات المعرّفة في قسم التطوير في ملف الإعداداتconfig/database.yml
. أما لو أردت تنفيذ عملية التهجير في بيئة أخرى، كبيئة الإنتاج Production Environment مثلًا، فيجب التصريح عن ذلك أثناء تنفيذ أمر التهجير وكما يلي:
bin/rails db:migrate RAILS_ENV=production.
حفظ البيانات بواسطة المتحكّم
سنعود الآن إلى المتحكّم ArticlesController
، حيث سنعمل على تعديل الحدث create
ليستخدم النموذج الجديد Article
لحفظ البيانات في قاعدة البيانات. افتح الملف app/controllers/articles_controller.rb
وعدّله بالصورة التالية:
def create
@article = Article.new(params[:article])
@article.save
redirect_to @article
end
يمكن استحداث initialize كل نموذج في Rails مع الخصائص Attributes المرتبطة به، والتي يتم ربطها تلقائيًا مع الأعمدة المقابلة في قاعدة البيانات. وقد قمنا بذلك في السطر الأول في الحدث create
(هل تذكر params[:article]
والذي يضمّ الخصائص التي نريدها). بعد ذلك يمكن حفظ النموذج في قاعدة البيانات من خلال الدالة @article.save
. وفي النهاية نعيد توجيه المستخدم إلى الحدث show
الذي سنعرّفه في وقت لاحق.
Quoteملاحظة:
قد يخطر على بالك السؤال التالي: لماذا استخدمنا الحرفA
في الدالةArticle.new
في حين أن جميع المتغيّرات التي تشير إلى المقالات كانت تبدأ بحرف صغير؟ في هذا السياق نحن نشير إلى صنف يحمل اسمArticle
والمعرّف في الملفapp/models/article.rb
، وفي لغة Ruby يجب أن تحمل الأصناف أسماء تبدأ بأحرف كبيرة.
Quoteملاحظة:
سنرى فيما بعد أن الدالّة@article.save
تُرجع قيمة منطقية تشير إلى أن المقالة قد حُفظت أم لا.
توجّه الآن إلى العنوان http://localhost:3000/articles/new
وستتلقّى الخطأ التالي:
يدعم Rails العديد من مزايا الأمان التي تساعد في كتابة تطبيقات أمينة، ونحن الآن نتعامل مع إحدى هذه المزايا. تدعى هذه الميزة بالمعاملات القوية strong parameters والتي تجبرنا على تحديد المعاملات المسموح بها في الأحداث الموجودة ضمن المتحكم.
ما الفائدة من ذلك؟ صحيح أن القدرة على إضافة جميع المعاملات إلى النموذج دفعة واحدة وبصورة تلقائية يختصر الكثير من الجهد بالنسبة للمبرمج، إلا أنّ البرنامج يكون في هذه الحالة عرضة للاستخدامات الخبيثة. فماذا لو تمّ إنشاء طلب إلى الخادوم يتضمن استمارة إنشاء مقالة جديدة إضافة إلى حقول أخرى تحتوي على معلومات تضرّ بالتطبيق؟ سيتم إسناد المعلومات الإضافية بصورة شاملة “Mass Assignment” إلى النموذج ثم إلى قاعدة البيانات جنبًا إلى جنب مع البيانات الأصلية، وهذا قد يتسبب في تعطيل عمل برنامجك أو قد يحدث ما هو أسوأ من ذلك بكثير.
يجب علينا إذًا تحديد المعاملات المسموح بإدخالها إلى النموذج، وفي حالتنا هذه سنسمح بإدراج معاملي title
و text
ونطلب توفّر قيم لهما. وللقيام بذلك عدّل السطر الأول من حدث 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
عرض المقالات
إن قمت بتعبئة استمارة المقالة الجديدة وإرسالها فستتلقّى خطأ مفاده عدم عثور Rails على الحدث 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
. ويمكن اتّباع الترتيب الذي يعجبك، ولكن تذكّر أن هذه التوابع هي توابع عامّة public
، ويجب الإعلان عنها قبل الإعلان عن التوابع الخاصّة.
سنضيف الآن الحدث show
آخذين ما سبق بعين الاعتبار:
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
end
def new
end
# بقيّة الشيفرة .....
استخدمنا الدالة Article.find
للبحث عن المقالة المطلوبة، وذلك بتمرير المعامل params[:id]
للحصول على قيمة المعرّف من الطلب الذي أرسلته صفحة إنشاء مقالة جديدة. كذلك استخدمنا متغيّرًا من نوع instance
(مسبوقًا بعلامة @) ليكون مرجعًا لكائن المقالة، وذلك لأنّ Rails يمرّر هذا النوع من المتغيّرات إلى العرض.
أنشئ الآن ملفًّا جديدًا باسم show.html.erb
في المسار app/views/articles/
وأضف إليه الشيفرة التالية:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
ستكون الآن قادرًا على إنشاء مقالة جديدة؛ لذا توجّه إلى العنوان http://localhost:3000/articles/new
وجرّب إضافة مقالة جديدة.
عرض قائمة بمقالات المدوّنة
نحتاج الآن إلى عرض قائمة بجميع المقالات الموجودة في المدونة، وسيكون المسار المرتبط بهذا الحدث وبحسب مخرجات الأمر bin/rails routes
كالتالي:
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
# بقية الشيفرة ...
بعدها أضف العرض الخاصّ بهذا الحدث والموجود في المسار app/views/articles/index.html.erb
والذي يتضمّن الشيفرة التالية:
<h1>Listing articles</h1>
<table>
<tr>
<th>Title</th>
<th>Text</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
هو أحد دوال العروض المساعدة والمضمّنة في Rails، ووظيفة هذا التابع إنشاء رابط تشعّبي بالاستناد إلى النصّ الذي نمرّره إليه، وهو في حالتنا هذه المسار الخاص بسرد قائمة المقالات.
لنضف بعض الروابط إلى العروض الأخرى، ولنبدأ بإضافة رابط إنشاء مقالة جديدة إلى الملف app/views/articles/index.html.erb
قبل وسم <table>
:
<%= link_to 'New article', new_article_path %>
سيوجّه هذا الرابط المستخدم إلى الصفحة التي تتضمن استمارة إنشاء مقالة جديدة.
سنضيف رابطًا آخر إلى الملفّ app/views/articles/new.html.erb
بعد الاستمارة مباشرة، ليتمكن المستخدم من العودة إلى الصفحة الرئيسية:
<%= form_for :article, url: articles_path do |f| %>
...
<% end %>
<%= link_to 'Back', articles_path %>
وأخيرًا، سنضيف رابطًا إلى القالب app/views/articles/show.html.erb
يوجّه المستخدم إلى الصفحة الرئيسية أيضًا، وبهذا يصبح بميسور من يستعرض مقالة معيّنة أن يرجع إلى الصفحة التي تعرض جميع المقالات:
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Text:</strong>
<%= @article.text %>
</p>
<%= link_to 'Back', articles_path %>
Quoteملاحظة:
إن كان الرابط يشير إلى حدث موجود في نفس المتحكّم فلا حاجة حينئذٍ إلى تحديد قيمة الخيار:controller
لأنّ Rails يستخدم المتحكّم الحالي بصورة افتراضية.
Quoteملاحظة:
في بيئة التطوير (وهي البيئة الافتراضية التي تعمل عليها حاليًّا) يقوم Rails بإعادة تحميل التطبيق في كلّ مرّة يتلقى فيها طلبًا، لذا لا حاجة لإيقاف تشغيل الخادوم وإعادة تشغيله لمشاهدة التحديثات.
التحقّق من المدخلات
لو نظرت إلى النموذج الذي أنشأناه سابقًا فسترى أنّ الملف بسيطٌ للغاية:
class Article < ApplicationRecord
end
لاحظ أنّ الصنف Article
موروث من الصنف ApplicationRecord
وهو بدوره موروث من الصنف ActiveRecord::Base
والذي يتضمّن الكثير من الوظائف والإجراءات الخاصة بالنماذج، مثل عمليات CRUD البسيطة (Create, Read, Update, Destroy) والتحقّق من البيانات Validation، إضافة إلى عمليات البحث المعقّدة وربط النماذج المختلفة مع بعضها البعض.
ويقدّم إطار العمل Rails توابع متعدّدة تساعد في التحقق من البيانات المرسلة إلى النموذج. افتح الملف app/models/article.rb
وأضف إليه الشيفرة التالية:
class Article < ApplicationRecord
validates :title, presence: true,
length: { minimum: 5 }
end
سيضمن هذا التغيير امتلاك كل مقالة جديدة في المدونة لعنوان يتألف من خمسة أحرف على الأقل. يتيح Rails التحقّق من أمور متنوّعة في النماذج، مثل التحقّق من وجود أو عدم تكرار الأعمدة والتحقّق من تنسيقها ووجود كائنات مرتبطة بها.
لنجرّب الآن استدعاء الدالة @article.save
في مقالة لا تمتلك عنوانًا وسنلاحظ أن الدالة ترجع القيمة false
. لو عدنا إلى المتحكّم في الملف app/controllers/articles_controller.rb
مرّة أخرى سنلاحظ بأنّنا لم نتحقّق من النتيجة التي ترجعها الدالة @article.save
ضمن الحدث create
. إن فشلت الدالة @article.save
في أداء عملها، يجب أن نعيد المستخدم إلى استمارة إضافة مقالة جديدة، وللقيام بذلك عدّل حدثي new
و create
في الملف app/controllers/articles_controller.rb
بالصورة التالية:
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
يحمل الاسم @article
وستتعرّف إلى سبب القيام بذلك بعد قليل.
لاحظ أنّنا استخدمنا render
داخل الحدث create
بدلًا من redirect_to
في حال إرجاع الدالة save
للقيمة false
. يستخدم التابع render
لكي يتم تمرير الكائن @article
إلى القالب الجديد عند تصييره. وعملية التصيير هذه تتم ضمن نفس الطلب الناتج من إرسال الاستمارة، في حين أن الدالة redirect_to
تتسبّب في إرسال طلب آخر.
الآن أعد تحميل الصفحة ذات العنوان http://localhost:3000/articles/new
وحاول إضافة مقالة جديد دون عنوان، سترى بأنّ Rails يعيدك إلى صفحة الاستمارة، ولكن هذا ليس مفيدًا جدًّا. يجب إخبار المستخدم بحدوث خطأ ما، وللقيام بذلك عدّل الملف app/views/articles/new.html.erb
للتحقّق من رسائل الخطأ:
<%= form_for :article, url: articles_path do |f| %>
<% 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>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', articles_path %>
تحقّقنا في البداية من وجود أي أخطاء من خلال @article.errors.any?
، وفي حال وجودها نعرض قائمة الأخطاء المتوفّرة من خلال @article.errors.full_messages
.
تأخذ الدالة pluralize
معاملين الأول رقمي والثاني نصّي. إن كان العدد أكبر من واحد تتحوّل السلسلة النصّية تلقائيًا إلى صيغة الجمع.
إن سبب إضافة @article = Article.new
إلى المتحكّم ArticlesController
هو أنّنا لو لم نقم بذلك لأصبحت قيمة المتغيّر @articl
هي nil
، وسيؤدي الاستدعاء @article.errores.any?
إلى إطلاق خطأ.
يحيط Rails الحقول التي تحتوي على أخطاء بوسم <div>
مع صنف CSS يحمل الاسم field_with_errors
، ويمكنك تعريف صنف CSS هذا لتنسيق الحقول حسب الرغبة.
والآن ستتلقّى رسالة خطا مرتّبة عندما تحاول حفظ مقالة لا تتضمن عنوانًا.
في الدرس القادم سنواصل العمل على النموذج حيث سنكتب الشيفرة المسؤولة عن تعديل المقالات وحذفها، ثم سنتعرّف على طريقة إنشاء علاقات بين النماذج المختلفة من خلال إضافة نموذج للتعامل مع التعليقات في المدونة.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.