نظرة خاطفة على وحدة التحكم في ريلز
ستتعرف في هذا الدليل على كيفية عمل وحدات التحكم (Controller) وكيفية ملاءمتها مع دورة الطلب (request cycle) في التطبيق الخاص بك.
بعد قراءة هذا الدليل، ستتعلم:
- كيفية اتباع تدفق الطلب من خلال وحدة تحكم.
- كيفية تقييد المعاملات التي مُرّرت إلى وحدة التحكم الخاصة بك.
- كيف ولماذا تخزَّن البيانات في الجلسة (session) أو ملفات تعريف الارتباط (cookies).
- كيفية العمل مع المرشحات (filters) لتنفيذ التعليمات البرمجية أثناء معالجة الطلب.
- كيفية استخدام استيثاق HTTP المضمن في وحدة التحكم.
- كيفية تدفق البيانات مباشرة إلى متصفح المستخدم.
- كيفية ترشيح المعاملات الحساسة بحيث لا تظهر في سجل التطبيق.
- كيفية التعامل مع الاستثناءات التي قد تنشأ أثناء معالجة الطلب.
ما الذي تفعله وحدة التحكم؟
وحدة التحكم Action Controller هي الحرف C في نمط التصميم MVC (اختصار للعبارة Model View Controller). بعد أن يحدِّد جهاز التوجيه (router) أي وحدة تحكم تُستخدَم لطلب ما، تكون وحدة التحكم مسؤولة عن فهم الطلب وإنتاج الناتج المناسب.
لحسن الحظ، تقوم وحدة التحكم Action Controller بمعظم المهام الأساسية الخاصة بك ويستخدم المواثيق الذكية (smart conventions) لجعل هذا الأمر بسيطًا قدر الإمكان.
بالنسبة لمعظم تطبيقات RESTful التقليدية، ستتلقى وحدة التحكم الطلب (وهذا غير مرئي بالنسبة لك كمطور)، وتجلب البيانات أو تحفظها من النموذج وتستخدم الواجهة لإنشاء مخرجات HTML. إذا كانت وحدة التحكم بحاجة إلى عمل الأشياء بطريقة مختلفة قليلًا، فهذه ليست مشكلة؛ هذه فقط الطريقة الأكثر شيوعًا لتعمل وحدة التحكم.
وبالتالي، يمكن اعتبار وحدة التحكم كوسيط بين النماذج والواجهات. فهي تجعل بيانات النموذج متاحة للعرض حتى تمكنه من عرض تلك البيانات للمستخدم، وتحفظ أو تُحدِّث بيانات المستخدم إلى النموذج.
لمزيد من التفاصيل حول عملية التوجيه، راجع صفحة التوجيه من الخارج إلى الداخل.
اتفاقية التسمية في وحدة التحكم
تفضل اتفاقية التسمية لوحدات التحكم في ريلز صيغة الجمع للكلمة الأخيرة في اسم وحدة التحكم، على الرغم من أنه ليس مطلوبًا تمامًا (مثل ApplicationController
).
على سبيل المثال، ClientsController
أفضل من ClientController
و SiteAdminsController
أفضل من SiteAdminController
أو SitesAdminsController
، وهكذا دواليك.
تسمح لك اتباع هذه الاتفاقية باستخدام مولدات المسارات الافتراضية (مثل resources
...إلخ.) دون الحاجة إلى تأهيل كل من المسار :path
أو وحدة التحكم :controller
، وسيبقى استخدام العنوان URL ودوال المسار المساعدة مترابطًا في جميع أنحاء التطبيق الخاص بك.
راجع دليل التخطيطات والتصيير لمزيد من التفاصيل.
ملاحظة: تختلف اتفاقية تسمية وحدة التحكم من اتفاقية تسمية النماذج، والتي من المتوقع أن تُسمّى بصيغة المفرد.
التوابع والإجراءات
وحدة التحكم هي صنف من أًصناف روبي ترث من ApplicationController
ولها توابع مثل أي صنف آخر. عندما يتلقى تطبيقك طلبًا، سيحدد التوجيه وحدة التحكم والإجراء المطلوب تشغيله، ثم تنشئ ريلز نسخةً لوحدة التحكم هذه ويشغل التابع الذي يحمل نفس اسم الإجراء.
class ClientsController < ApplicationController
def new
end
end
على سبيل المثال، إذا انتقل المستخدم إلى /clients/new في تطبيقك، لإضافة عميل جديد، ستنشئ ريلز نسخةً من ClientsController
وسيستدعي تابعًا جديدًا. لاحظ أن التابع الفارغ من المثال أعلاه سيعمل على ما يرام لأن ريلز افتراضيًا تقدم واجهة new.html.erb ما لم ينص الإجراء على خلاف ذلك. يمكن أن يتيح التابع الجديد واجهة متغير نسخة client@
عن طريق إنشاء عميل جديد:
def new
@client = Client.new
end
يوضح دليل التخطيطات والتصيير هذا الأمر بمزيد من التفصيل.
يرث ApplicationController
من ActionController::Base
، الذي يحدد عددًا من التوابع المفيدة. سيغطي هذا الدليل بعضًا من هذه الأمور، ولكن إذا كنت مهتمًا برؤية ما هناك، فيمكنك الاطلاع عليها جميعًا في توثيق واجهة برمجة التطبيقات عبر هذا الرابط.
فقط التوابع العامة هي قابلة للاستدعاء كإجراء. من أفضل الممارسات تقليل رؤية التوابع (مع الخصوصية أو الحماية) التي لا يُقصَد بها أن تكون إجراءات، مثل التوابع المساعدة أو المرشحات.
المعاملات
ربما تريد الوصول إلى البيانات المرسلة بواسطة المستخدم أو المعاملات الأخرى في وحدة التحكم الخاصة بك. هناك نوعان من المعاملات الممكنة في تطبيق الويب: الأولى هي المعاملات التي ترسل كجزء من عنوان URL، وتسمى معاملات سلسلة الاستعلام. سلسلة الاستعلام هي كل شيء بعد الرمز "?" في عنوان URL. عادة ما يشار إلى النوع الثاني من المعاملات ببيانات POST.
عادةً ما تأتي هذه المعلومات من نموذج HTML ملئ بواسطة المستخدم. يطلق عليها بيانات POST لأنه لا يمكن إرسالها إلا كجزء من طلب HTTP POST. لا تميز ريلز بين معاملات سلسلة الاستعلام ومعاملات POST، وكلاهما متاح في params
التي من النوع Hash
في وحدة التحكم الخاصة بك:
class ClientsController < ApplicationController
# HTTP GET يستخدم هذا الإجراء معاملات سلسلة الاستعلام لأنه يشغل بواسطة طلب
# ولكن هذا لا يحدث أي فرق في الطريقة التي يصل بها إلى المعاملات. سيبدو
# لهذا الإجراء على هذا النحو من أجل قائمة العملاء المفعلة URL عنوان
# /clients?status=activated
def index
if params[:status] == "activated"
@clients = Client.activated
else
@clients = Client.inactivated
end
end
# HTML هي على الأغلب قادمة من نموذج .POST يستخدم هذا الإجراء معاملات
# "/clients" هو RESTful لطلب URL الذي أرسله المستخدم. سيكون عنوان
# الطلب (body) وترسل البيانات كجزء من نص
def create
@client = Client.new(params[:client])
if @client.save
redirect_to @client
else
# يستبدل هذا السطر سلوك التصيير الافتراضي، والذي كان من شأنه
# "create" تصيير الواجهة
render "new"
end
end
end
المعاملات ذات النوع Array
و Hash
لا تقتصر المعاملات params
ذات النوع Hash
على مفاتيح وقيم أحادية البعد. يمكن أن تحتوي على مصفوفات وجداول Hash
متداخلة. لإرسال مجموعة من القيم، ألحق زوجًا فارغًا من الأقواس المعقوفة "[]
" باسم المفتاح:
GET /clients?ids[]=1&ids[]=2&ids[]=3
ملاحظة: يشفر عنوان URL الفعلي في هذا المثال على أنه"/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3"
لأن المحرف "[" و "]" غير مسموح به في عناوين URL. في معظم الأوقات، لا داعي للقلق حيال ذلك لأنَّ المتصفح يرمزها لك، وتفك ريلز ترميزها تلقائيًا؛ ولكن، إذا وجدت نفسك مضطرًّا لإرسال تلك الطلبات إلى الخادم يدويًا، يجب أن تضع ذلك بالحسبان.
ستكون قيمة [params[:ids
الآن ["1" ، "2" ، "3"]
. لاحظ أن قيم المعاملات هي دائما سلاسل نصيّة؛ لا تحاول ريلز تخمين أو تحويل النوع.
ملاحظة: تستبدل القيم مثل [nil]
أو [nil, nil, ...]
في params
بالقوسين المعقوفين []
لأسباب أمنية بشكل افتراضي. انظر دليل الأمن لمزيد من المعلومات.
لإرسال قيمة من النوع hash، عليك تضمين اسم المفتاح داخل الأقواس:
<form accept-charset="UTF-8" action="/clients" method="post">
<input type="text" name="client[name]" value="Acme" />
<input type="text" name="client[phone]" value="12345" />
<input type="text" name="client[address][postcode]" value="12345" />
<input type="text" name="client[address][city]" value="Carrot City" />
</form>
عند إرسال هذا النموذج، ستكون قيمة المعاملات [:client]
هي:
{ "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }
لاحظ التجزئة المتداخلة في المعاملات [:client][:address]
.
يعمل الكائن params
مثل Hash
، ولكن يتيح لك استخدام الرموز والسلاسل النصية بالتبادل كمفاتيح.
معاملات JSON
إذا كنت تكتب تطبيقًا لخدمة ويب، فقد تجد نفسك أكثر راحة في قبول المعاملات بتنسيق JSON. إذا تعين ترويسة الطلب "Content-Type" من طلبك إلى "application/json"، تحمّل ريلز تلقائيًا المعاملات في params
من النوع Hash
، والتي يمكنك الوصول إليها كما تفعل عادةً.
على سبيل المثال، إذا كنت ترسل محتوى JSON التالي:
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
ستتلقى وحدة التحكم الخاصة بك [params[:company
بالشكل { "name" => "acme", "address" => "123 Carrot Street"}
.
أيضًا، إذا شغلت config.wrap_parameters
في مُهيئك (initializer) أو استدعيت wrap_parameters
في وحدة التحكم الخاصة بك، فيمكنك بأمان حذف العنصر الجذر ((root element
في المعامل JSON
. في هذه الحالة، ستستنسخ المعاملات وتغلف بمفتاح يحدد استنادًا إلى اسم وحدة التحكم الخاصة بك. لذلك يمكن كتابة طلب JSON أعلاه على النحو التالي:
{ "name": "acme", "address": "123 Carrot Street" }
وبافتراض أنك ترسل البيانات إلى CorporateController
، فستغلف في المفتاح :company
كما يلي:
{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }
يمكنك تخصيص اسم المفتاح أو معاملات معينة تريد تغليفها عن طريق الاطلاع على توثيق الواجهة البرمجية.
ملاحظة: استُخرِج دعم تحليل معاملات XML في جوهرة (gem) تسمى actionpack-xml_parser
.
معاملات التوجيه
تحتوي المعاملات params
التي من النوع Hash
دومًا على مفاتيح وحدة التحكم :controller
والإجراء :action
، ولكن يجب استخدام التابعين control_name
و action_name
بدلًا من ذلك للوصول إلى هذه القيم. أي معاملات أخرى يحددها التوجيه، مثل :id
ستكون متوفرة أيضًا. على سبيل المثال، ضع في اعتبارك قائمة العملاء حيث يمكن أن تعرض القائمة عملاء نشطين أو غير نشطين. يمكننا إضافة مسار يلتقط معامل الحالة :status
في عنوان "pretty" URL:
get '/clients/:status' => 'clients#index', foo: 'bar'
في هذه الحالة، عندما يفتح المستخدم العنوان URL/clients/active، سيتم تعيين المعاملات [:status]
إلى "نشط" (active). عند استخدام هذا المسار، سيعين أيضًا المعاملات [:foo]
إلى "bar"، كما لو مررت في سلسلة الاستعلام. ستتلقى وحدة التحكم أيضًا [params[:action
بالشكل "index" و [params[:controller
بالشكل "clients".
التابع default_url_options
يمكنك تعيين المعاملات الافتراضية العامة لإنشاء عنوان URL من خلال تحديد تابع يسمى default_url_options
في وحدة التحكم الخاصة بك. يجب أن يعيد هذا التابع جدول hash مع الإعدادات الافتراضية المطلوبة، والتي يجب أن تكون مفاتيحها رموزًا بالشكل التالي:
class ApplicationController < ActionController::Base
def default_url_options
{ locale: I18n.locale }
end
end
تستخدم هذه الخيارات كنقطة بداية عند إنشاء عناوين URL، لذلك من الممكن أن تتجاوز من خلال الخيارات التي مُرِّرت إلى url_for
للمكالمات.
إذا قمت بتعريف default_url_options
في ApplicationController
، كما هو موضح في المثال أعلاه، فستُستخدَم هذه الإعدادات الافتراضية لجميع عمليات إنشاء عناوين URL. يمكن تعريف الطريقة أيضًا في وحدة تحكم معينة، وفي هذه الحالة لا تؤثر سوى على عناوين URL التي أنشئت هناك.
في طلب معين معطى، لا يُستدعَى التابع فعليًا لكل عنوان URL أُنشِئ؛ لأسباب الأداء، يخزَّن الجدول hash المعاد في ذاكرة التخزين المؤقتة الداخلية، إذ يوجد استدعاء واحد على الأكثر لكل طلب.
المعاملات القوية
مع استخدام المعاملات القوية (strong parameters)، يُحظَر استخدام معاملات وحدة التحكم في التعيينات الشاملة للنموذج النشط حتى تدرج في القائمة البيضاء. وهذا يعني أنه سيتعين عليك اتخاذ قرار واعي بشأن الخاصيات (attributes) التي تسمح بإجراء التحديث الشامل. هذه ممارسة أمان أفضل للمساعدة في منع المستخدمين عن طريق الخطأ من تحديث سمات الطرازات الحساسة.
بالإضافة إلى ذلك، يمكن وضع علامة على المعاملات على النحو المطلوب، وستتدفق من خلال تدفق اطلاق/إنقاذ (raise/rescue) محدَّد مسبقًا، مما يؤدي إلى إعادة الخطأ 400 للطلب إذا لم تمرر جميع المعاملات المطلوبة.
class PeopleController < ActionController::Base
# ActiveModel::ForbiddenAttributesError سيؤدي هذا إلى اطلاق الاستثناء
# نظرًا لأنه يستخدم تعيينًا شامًلا بدون خطوة تصريح صريحة.
def create
Person.create(params[:person])
end
# person طالما هناك المفتاح (flying colors) سوف يمر هذا مع الألوان الطائرة
# ActionController::ParameterMissing في المعاملات، وإلا فإنه سيطلق الاستثناء
# ويحول إلى الخطأ 400 في ActionController::Base، والذي سيقبض عليه من قبل
# .الطلب
def update
person = current_account.people.find(params[:id])
person.update!(person_params)
redirect_to person
end
private
# لتغليف المعاملات المسموح بها هو مجرد نموذج private إن استخدام التابع
# جيد حيث ستتمكن من إعادة استخدام نفس قائمة التصاريح بين الإنشاء
# والتحديث. يمكنك تخصيص هذه الطريقة مع التحقق من كل مستخدم للخاصيات
# .المسموح بها
def person_params
params.require(:person).permit(:name, :age)
end
end
القيم العددية المسموح بها
إن اعطاء
params.permit(:id)
المفتاح :id
سيمرر القائمة البيضاء إذا ظهرت في المعاملات وكان لها قيمة قياسية مسموح بها. خلاف ذلك، سيرشح المفتاح، لذلك لا يمكن حقن المصفوفات أو القيم hash أو أي كائنات أخرى.
الأنواع القياسية المسموح بها هي String
و Symbol
و NilClass
و Numeric
و TrueClass
و FalseClass
و Date
و Time
و DateTime
و StringIO
و IO
و ActionDispatch::Http::UploadedFile
و Rack::Test::UploadedFile
.
لتوضيح أن القيمة في params
يجب أن تكون مصفوفة من القيم العددية المسموح بها، قم بتعيين المفتاح إلى مصفوفة فارغة:
params.permit(id: [])
في بعض الأحيان، لا يكون من الممكن أو الملائم الإعلان عن المفاتيح الصالحة لمعامل hash أو هيكلها الداخلي. ما عليك سوى التعيين إلى جدول hash فارغ:
params.permit(preferences: {})
لكن كن حذرًا لأن هذا يفتح الباب أمام الإدخال التعسفي. في هذه الحالة، يضمن التصريح أن القيم في البنية المعادة مسموح بها كإصلاحات ومرشحات من أي شيء آخر.
لإدراج كل محتوى جدول hash من المعاملات إلى القائمة البيضاء، يمكن استخدام التابع permit!
على النحو التالي:
params.require(:log_entry).permit!
تدل :log_entry
على معاملات hash وأية hash فرعية لها كما هو مسموح به ولا يتحقق من القياسات (scalars) المسموح بها، إذ يقبل أي شيء. يجب توخي الحذر الشديد عند استخدام permit!
، حيث أنه سيسمح بتعيين شامل لجميع خصائص النموذج الحالية والمستقبلية.
المعاملات المتداخلة
يمكنك أيضًا استخدام التابع permit
على المعاملات المتداخلة، مثل:
params.permit(:name, { emails: [] },
friends: [ :name,
{ family: [ :name ], hobbies: [] }])
يضيف هذا الإعلان إلى القائمة خاصيات الاسم (name) والبريد الإلكتروني (emails) والأصدقاء (friends) ...إلخ. من المتوقع أن تكون عناوين البريد الإلكتروني عبارة عن مصفوفة من القيم العددية المسموح بها، وأن يكون الأصدقاء مصفوفة من الموارد ذات خاصيات (attributes) محددة، إذ يجب أن يكون لها الخاصية name
(أية قيم قياسية مسموح بها)، والخاصية hobbies
كمصفوفة من القيم العددية المسموح بها، والخاصية family
التي تقتصر على وجود اسم (أي القيم العددية المسموح بها يمكن استعمالها هنا أيضًا).
مزيد من الأمثلة
قد ترغب أيضًا في استخدام الخاصيات المسموح بها في الإجراء الجديد. هذا يثير المشكلة التي لا يمكنك استخدام require
على مفتاح الجذر لأنه، عادةً، لا وجود لها عند استدعاء new
:
# يمكنك توفير قيمة افتراضية واستخدام معاملات ،`fetch` باستخدام
# واجهة برمجية قوية من هناك
params.fetch(:blog, {}).permit(:title, :author)
يسمح لك تابع صنف النموذج accepts_nested_attributes_for
بتحديث السجلات المرتبطة وتدميرها. يعتمد هذا على المعاملات id
و _destroy:
# permit :id and :_destroy
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])
تُعامَل الكائنات Hash
التي مفاتيحها هي أعداد صحيحة بشكل مختلف، ويمكنك الإعلان عن الخاصيات كما لو كانت أبناءً مباشرين. يمكنك الحصول على هذه الأنواع من المعاملات عند استخدامك accepts_nested_attributes_for
بالاقتران مع has_many
المقابل:
# :لإضافة البيانات التالية إلى القائمة البيضاء
# {"book" => {"title" => "Some Book",
# "chapters_attributes" => { "1" => {"title" => "First Chapter"},
# "2" => {"title" => "Second Chapter"}}}}
params.require(:book).permit(:title, chapters_attributes: [:title])
خارج نطاق المعاملات القوية
صُمِّمت واجهة برمجة التطبيقات (API) للمعامل القوي مع أخذ حالات الاستخدام الأكثر شيوعًا في الحسبان. لا يقصد به العصا السحرية التي تعالج جميع مشاكل القائمة البيضاء. ومع ذلك، يمكنك بسهولة خلط الواجهة البرمجية مع التعليمات البرمجية الخاصة بك للتكيف مع الموقف الخاص بك.
تخيل السيناريو التالي: لديك معاملات تمثل اسم منتج وجدول hash من البيانات التعسفية المرتبطة بهذا المنتج، وتريد إدراج خاصية اسم المنتج في القائمة البيضاء وكذلك تجزئة البيانات بالكامل. لا تسمح لك واجهة برمجة التطبيقات للمعاملات القوية بإدراج كل التجزئة المتداخلة في القائمة البيضاء بأية مفاتيح مباشرة، ولكن يمكنك استخدام مفاتيح التجزئة المتشعبة (nested hash) لتوضيح ما ترغب في إضافته إلى القائمة البيضاء:
def product_params
params.require(:product).permit(:name, data: params[:product][:data].try(:keys))
end
الجلسة
يحتوي تطبيقك على جلسة (Session) لكل مستخدم حيث يمكنك تخزين كميات صغيرة من البيانات التي ستستمر بين الطلبات. لا تتوفر الجلسة إلا في وحدة التحكم والملف الشخصي ويمكن استخدام إحدى آليات التخزين المختلفة التالية:
ActionDispatch::Session::CookieStore
- يخزن كل شيء عند العميل.ActionDispatch::Session::CacheStore
- تخزن البيانات في ذاكرة ريلز للتخزين المؤقت.ActionDispatch::Session::ActiveRecordStore
- تخزن البيانات في قاعدة بيانات باستخدام Active Record. هذا يتطلب الجوهرةactiverecord-session_store
.ActionDispatch::Session::MemCacheStore
- تخزن البيانات في كتلةmemcached
(هذا تنفيذ قديم؛ يجب مراعاة استخدامCacheStore
بدلًا من ذلك).
تستخدم جميع مخازن جلسات العمل ملف تعريف الارتباط (cookie) لتخزين معرف فريد لكل جلسة (يجب عليك استخدام ملف تعريف الارتباط، ولن تسمح لك ريلز بتمرير معرف الجلسة في عنوان URL لأن هذا أقل أمانًا).
بالنسبة إلى معظم المخازن، يُستخدَم هذا المعرّف ID للبحث عن بيانات الجلسة على الخادم، على سبيل المثال، في جدول قاعدة البيانات. يوجد استثناء واحد، وهو مخزن الجلسة الافتراضي والموصى به - مخزن ملفات تعريف الارتباط CookieStore
- الذي يخزن كافة بيانات جلسة العمل في ملف تعريف الارتباط نفسه (لا يزال المعرف ID متاحًا لك إذا كنت بحاجة إليه). هذا يتميز بكونه خفيف الحجم للغاية ويتطلب إعدادًا صفريًّا (zero setup) في تطبيق جديد من أجل استخدام الجلسة. توقَّع بيانات ملفات تعريف الارتباط لجعلها غير قابلة للتلاعب. كما تشفر بحيث لا يستطيع أي شخص لديه حق الوصول إليها لقراءة محتوياتها. (لن تقبلها ريلز إذا عُدِّلَت).
يمكن باستعمال الآلية CookieStore
تخزين حوالي 4 كيلوبايت من البيانات - أقل بكثير من البيانات الأخرى - ولكن هذا عادةً ما يكون كافيًا. لا تخزن كميات كبيرة من البيانات في الجلسة بغض النظر عن آلية تخزين الجلسة التي يستخدمها تطبيقك. يجب تجنب تخزين الكائنات المعقدة (أي شيء آخر غير كائنات روبي الأساسية، إذ يعدُّ المثال الأكثر شيوعًا هو النماذج) في الجلسة، حيث قد يتعذر على الخادم إعادة تجميعها بين الطلبات، مما يؤدي إلى حدوث خطأ.
إذا لم تخزن جلسات المستخدم الخاصة بك البيانات المهمة أو لا تحتاج إلى أن تكون موجودة لفترات طويلة (على سبيل المثال إذا كنت تستخدم الفلاش [flash] فقط للمراسلة)، فيمكنك استخدام ActionDispatch::Session::CacheStore
. سيخزن ذلك الجلسات باستخدام تطبيق ذاكرة التخزين المؤقت الذي قمت بتكوينه للتطبيق الخاص بك. وتتمثل ميزة ذلك في أنه يمكنك استخدام البنية الأساسية المؤقتة الموجودة لديك لتخزين الجلسات دون الحاجة إلى أي إعداد إضافي أو إدارة إضافية. الجانب السلبي، بالطبع، هو أن الجلسات ستكون سريعة الزوال ويمكن أن تختفي في أي وقت.
اقرأ المزيد حول تخزين الجلسة في دليل الأمان.
إذا كنت بحاجة إلى آلية تخزين جلسة عمل مختلفة، فيمكنك تغييرها في المُهيئ (initializer):
# استخدم قاعدة البيانات للجلسات بدلًا من الإعداد الافتراضي الذي يعتمد على
# ملف تعريف الارتباط، والذي لا ينبغي استخدامه لتخزين معلومات سرية للغاية
# ("rails g active_record: session_migration" أنشئ جدول الجلسة باستخدام)
Rails.application.config.session_store :active_record_store
تقوم ريلز بإعداد مفتاح الجلسة (اسم ملف تعريف الارتباط) عند توقيع بيانات الجلسة. يمكن أيضًا تغيير هذه في المُهيئ:
# تأكد من إعادة تشغيل الخادم الخاص بك عند تعديل هذا الملف.
Rails.application.config.session_store: cookie_store، key: '_your_app_session'
يمكنك أيضًا تمرير المفتاح domain:
وتحديد اسم النطاق لملف تعريف الارتباط:
# تأكد من إعادة تشغيل الخادم الخاص بك عند تعديل هذا الملف.
Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
إعداد ريلز (من أجل CookieStore
) وهو مفتاح سري يستخدم لتوقيع بيانات الجلسة في config/credentials.yml.enc. هذا يمكن تغييره مع bin/rails credentials:edit
.
# aws:
# access_key_id: 123
# secret_access_key: 345
# في ريلز، بما في ذلك الواحد الذي MessageVerifiers يستخدم كأساس لجميع
# يحمي ملفات تعريف الارتباط
secret_key_base: 492f...
ملاحظة: سيؤدي تغيير secret_key_base
عند استخدام CookieStore
إلى إبطال جميع جلسات العمل الموجودة.
الوصول إلى الجلسة
في وحدة التحكم الخاصة بك، يمكنك الوصول إلى الجلسة من خلال نسخة التابع session
.
ملاحظة: تُحمَّل الجلسات برفق. إذا لم تدخل إلى الجلسات في شفرة الإجراء الخاص بك، فلن تُحمَّل. ومن ثم لن تحتاج أبدًا إلى تعطيل الجلسات، فقط لن يؤدي الوصول إليها إلى القيام بهذه المهمة.
تخزن قيم الجلسة باستخدام الأزواج مفتاح/قيمة مثل جدول hash:
class ApplicationController < ActionController::Base
private
# :current_user_id المخزن في الجلسة مع المفتاح ID البحث عن المستخدم بالمعرف
# هذه طريقة شائعة للتعامل مع تسجيل دخول المستخدم في تطبيق ريلز؛ تسجيل
# الدخول يحدد قيمة الجلسة وتسجيل الخروج يزيلها
def current_user
@_current_user ||= session[:current_user_id] &&
User.find_by(id: session[:current_user_id])
end
end
لتخزين شيء ما في الجلسة، ما عليك سوى تعيينه إلى المفتاح مثل جدول hash:
class LoginsController < ApplicationController
# هو تسجيل الدخول والذي يعرف بتسجيل دخول المستخدم "Create" إن
def create
if user = User.authenticate(params[:username], params[:password])
# احفظ مُعرِّف المستخدم في الجلسة حتى يمكن استخدامه في الطلبات اللاحقة
session[:current_user_id] = user.id
redirect_to root_url
end
end
end
لإزالة شيء ما من الجلسة، عيّن هذا المفتاح ليكون nil
:
class LoginsController < ApplicationController
# هو حذف تسجيل الدخول والذي يعرف بتسجيل خروج المستخدم "Delete" إن
def destroy
# قم بإزالة معرِّف المستخدم من الجلسة
@_current_user = session[:current_user_id] = nil
redirect_to root_url
end
end
لإعادة تعيين الجلسة بالكامل، استخدم reset_session
.
Flash
إنَّ Flash هو جزء خاص من الجلسة يمسح مع كل طلب. وهذا يعني أن القيم المخزنة هناك لن تكون متاحة إلا في الطلب التالي، وهو أمر مفيد في تمرير رسائل الخطأ وما إلى ذلك.
يتم الوصول إليه بنفس الطريقة مثل الجلسة، مثل hash (إنه نسخة من FlashHash
).
لنستخدم فعل تسجيل الخروج كمثال. تستطيع وحدة التحكم إرسال رسالة تعرض على المستخدم عند الطلب التالي:
class LoginsController < ApplicationController
def destroy
session[:current_user_id] = nil
flash[:notice] = "You have successfully logged out."
redirect_to root_url
end
end
لاحظ أنه من الممكن أيضًا تعيين رسالة flash كجزء من إعادة التوجيه. يمكنك تعيين :notice
، أو :alert
أو :flash
ذي الغرض العام:
redirect_to root_url, notice: "You have successfully logged out."
redirect_to root_url, alert: "You're stuck here!"
redirect_to root_url, flash: { referral_code: 1234 }
يعيد الإجراء destroy
التوجيه إلى root_url
للتطبيق، حيث تعرض الرسالة. لاحظ أن الأمر متروك تمامًا للإجراء التالي لتحديد، إذا كان هناك أي شيء، ما إذا كان سينفَّذ الإجراء السابق في flash. من المعتاد عرض أي تنبيهات خطأ أو إشعارات من flash في تخطيط التطبيق:
<html>
<!-- <head/> -->
<body>
<% flash.each do |name, msg| -%>
<%= content_tag :div, msg, class: name %>
<% end -%>
<!-- more content -->
</body>
</html>
بهذه الطريقة، إذا قام أحد الإجراءات بإعداد إشعار أو رسالة تنبيه، فسيعرضه المخطط تلقائيًا. يمكنك تمرير أي شيء يمكن أن تخزنه الجلسة؛ لا تقتصر على الإشعارات والتنبيهات:
<% if flash[:just_signed_up] %>
<p class="welcome">Welcome to our site!</p>
<% end %>
إذا كنت تريد نقل قيمة flash إلى طلب آخر، فاستخدم طريقة الاحتفاظ:
class MainController < ApplicationController
# ولكنك ترغب في إعادة توجيه جميع root_url لنفترض أن هذا الإجراء يتوافق مع
# flash إذا قام أحد الإجراءات بتعيين .UsersController#index الطلبات هنا إلى
# وإعادة التوجيه هنا، فستُفقَد القيم عادة عند حدوث عملية إعادة توجيه أخرى
# لجعلها تستمر لطلب آخر "keep" ولكن يمكنك استخدام
def index
# flash سوف تستمر كل قيم .
flash.keep
# يمكنك أيضًا استخدام مفتاح للاحتفاظ بنوع معين من القيمة فقط
# flash.keep(:notice)
redirect_to users_url
end
end
Flash.now
بشكل افتراضي، ستؤدي إضافة قيم إلى Flash إلى جعلها متاحة للطلب التالي، ولكن في بعض الأحيان قد ترغب في الوصول إلى هذه القيم في نفس الطلب. على سبيل المثال، إذا فشل إجراء الإنشاء create
في حفظ المصدر وقمت بعرض القالب الجديد new
مباشرةً، فلن يؤدي ذلك إلى طلب جديد، ولكن قد لا تزال ترغب في عرض الرسالة باستخدام flash. للقيام بذلك، يمكنك استخدام flash.now
بنفس طريقة استخدام flash العادي:
class ClientsController < ApplicationController
def create
@client = Client.new(params[:client])
if @client.save
# ...
else
flash.now[:error] = "Could not save client"
render action: "new"
end
end
end
ملفات تعريف الارتباط (Cookies)
يمكن للتطبيق تخزين كميات صغيرة من البيانات على متصفح العميل - تسمى ملفات تعريف الارتباط cookies (أو الكعكات)- والتي ستستمر عبر الطلبات وحتى الجلسات. توفر ريلز وصولًا سهلًا إلى ملفات تعريف الارتباط عبر التابع cookies
، والذي - تمامًا مثل الجلسة (session) - يعمل مثل الجدول hash:
class CommentsController < ApplicationController
def new
# الملء التلقائي لاسم المعلق إذا كان قد تم تخزينه في ملف تعريف ارتباط
@comment = Comment.new(author: cookies[:commenter_name])
end
def create
@comment = Comment.new(params[:comment])
if @comment.save
flash[:notice] = "Thanks for your comment!"
if params[:remember_name]
# تذكر اسم صاحب التعليق
cookies[:commenter_name] = @comment.author
else
# حذف ملف تعريف الارتباط لملف تعريف الارتباط الخاص بصاحب التعليق ، إن وجد.
cookies.delete(:commenter_name)
end
redirect_to @comment.article
else
render action: "new"
end
end
end
لاحظ حينما تقوم بتعيين المفتاح إلى القيمة nil
لقيم الجلسة، يجب عليك لحذف قيمة ملف تعريف ارتباط استخدام (cookies.delete(:key
.
توفر ريلز أيضًا وعاء ملفات تعريف ارتباط موقَّع ووعاءً لملف تعريف الارتباط مشفر لتخزين البيانات الحساسة. يُلحِق وعاء ملفات تعريف الارتباط توقيعًا مشفرًا على قيم ملفات تعريف الارتباط لحماية تكاملها. يشفِّر وعاء ملف تعريف الارتباط المشفر القيم بالإضافة إلى توقيعها، بحيث لا يمكن للمستخدم النهائي قراءتها. ارجع إلى توثيق الواجهة البرمجية (API) لمزيد من التفاصيل.
تستخدم هذه الأوعية الخاصة لملفات تعريف الارتباط تسلسلًا لإجراء تسلسل للقيم المخصصة في سلاسل وإلغاء تسلسلها إلى كائنات روبي في القراءة.
يمكنك تحديد برنامج التسلسل الذي يجب استخدامه:
Rails.application.config.action_dispatch.cookies_serializer = :json
التسلسل الافتراضي للتطبيقات الجديدة هو :json
. للتوافق مع التطبيقات القديمة مع ملفات تعريف الارتباط الموجودة، يُستخدَم :marshal
عند عدم تحديد خيار التسلسل.
يمكنك أيضًا تعيين هذا الخيار إلى :hybrid
، في هذه الحالة تعمل ريلز على إلغاء تسلسل ملفات تعريف الارتباط الموجودة (Marshal-serialized) بشفافية عند القراءة وإعادة كتابتها بتنسيق JSON. وهذا مفيد لترحيل التطبيقات الحالية إلى مُسلسِل (json (serializer:.
من الممكن أيضًا تمرير مُسلسِل (serializer) مخصص يستجيب للتحميل (load) والتفريغ (dump):
Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer
عند استخدام مسلسل :json
أو :hybrid
، يجب الحذر من أنه لا يمكن إجراء تسلسل لكافة كائنات روبي بالصيغة JSON. على سبيل المثال، سيُسَلسَل الكائنين Data
و Time
كسلسلة نصية (strings)، وقيم Hash
سوف يكون مفاتيحها سلسلة نصية بصيغة JSON (تدعى stringified).
class CookiesController < ApplicationController
def set_cookie
cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
redirect_to action: 'read_cookie'
end
def read_cookie
cookies.encrypted[:expiration_date] # => "2014-03-20"
end
end
من المستحسن تخزين البيانات البسيطة (السلاسل النصية والأعداد) في ملفات تعريف الارتباط. إذا كان عليك تخزين كائنات معقدة، فستحتاج إلى معالجة التحويل يدويًا عند قراءة القيم في الطلبات اللاحقة.
إذا كنت تستخدم مخزن جلسة ملفات تعريف الارتباط، فسيطبق ذلك على التجزئة session
و flash
أيضًا.
تصيير بيانات XML و JSON
يجعل ActionController
من السهل للغاية تقديم بيانات XML أو JSON. إذا كنت قد أنشأت وحدة تحكم باستخدام scaffolding
، فسيبدو الأمر كالتالي:
class UsersController < ApplicationController
def index
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render xml: @users }
format.json { render json: @users }
end
end
end
قد تلاحظ في الكود السابق أننا استخدمنا render xml: @users
وليس render xml: @users.to_xml
. إذا لم يكن الكائن عبارة عن سلسلة نصية، فستستدعي ريلز تلقائيًا to_xml
من أجلنا.
المرشحات
المرشحات (Filters) هي التوابع التي تُشغِّل إجراءات وحدة التحكم "before"، أو "after" أو "around".
المرشحات موروثة، لذلك إذا قمت بتعيين مرشح على ApplicationController
، سيشغل على كل وحدة التحكم في التطبيق الخاص بك.
قد تؤدي مرشحات "before" إلى إيقاف دورة الطلب. المرشح "before" الشائع هو الذي يتطلب تسجيل دخول المستخدم لإجراء ما. يمكنك تحديد طريقة الترشيح بهذه الطريقة:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
يقوم التابع ببساطة بتخزين رسالة خطأ في الفلاش (flash) ويعيد التوجيه إلى نموذج تسجيل الدخول إذا لم يقم المستخدم بتسجيل الدخول.في حالة عرض أو أعاد المرشح "before" التوجيه، فلن تنفذ الإجراء. إذا كانت هناك مرشحات إضافية مجدولة للتشغيل بعد ذلك المرشح، فستُلغَى أيضًا.
في هذا المثال، يضاف المرشح إلى ApplicationController
وبالتالي جميع وحدات التحكم في التطبيق ترثه. هذا سيجعل كل شيء في التطبيق يتطلب من المستخدم أن يقوم بتسجيل الدخول من أجل استخدامه. لأسباب واضحة (لن يتمكن المستخدم من تسجيل الدخول في المقام الأول!)، لا ينبغي أن تتطلب كل عناصر التحكم أو الإجراءات ذلك. يمكنك منع تشغيل هذا المرشح قبل تنفيذ إجراءات معينة باستخدام skip_before_action
:
class LoginsController < ApplicationController
skip_before_action :require_login, only: [:new, :create]
end
الآن، سيعمل الإجراءين new
و create
الذين يخصان LoginsController
كما من قبل دون الحاجة إلى تسجيل المستخدم. الخيار :only
يُستخدَم فقط لتخطي هذا المرشح لهذه الإجراءات، وهناك أيضًا الخيار :except
الذي يعمل بطريقة اخرى. يمكن استخدام هذه الخيارات عند إضافة المرشحات أيضًا، بحيث يمكنك إضافة مرشح لا يعمل إلا للإجراءات المحددة في المقام الأول.
ملاحظة: لن يعمل استدعاء المرشح نفسه عدة مرات بخيارات مختلفة، نظرًا لأن آخر تعريف للمرشح سيحل محل المرشح السابق.
مرشحات After ومرشحات Around
بالإضافة إلى المرشحات "before"، يمكنك أيضًا تشغيل مرشحات بعد تنفيذ إجراء ما، أو قبل وبعد التنفيذ كليهما.
المرشحات "after" تشبه المرشحات "before"، ولكن نظرًا لأنَّ الإجراء قد شُغِّل بالفعل، فسيكون بإمكانهم الوصول إلى بيانات الاستجابة التي على وشك إرسالها إلى العميل. من الواضح أن المرشحات "after" لا يمكنها إيقاف الإجراء من التشغيل. يرجى ملاحظة أن المرشحات "after" تُنفَّذ فقط بعد إجراء ناجح، ولكن ليس عندما يرفع استثناء في دورة الطلب.
المرشحات "around" مسؤولة عن إدارة الإجراءات المرتبطة بها من خلال العائد (yielding)، على غرار الطريقة التي تعمل بها برمجيات Rack الوسيطة.
على سبيل المثال، في أحد مواقع الويب التي تحتوي على تغييرات في سير عمل الموافقة، يمكن للمسؤول معاينتها بسهولة، إذ ما عليك سوى تطبيقها في transaction:
class ChangesController < ApplicationController
around_action :wrap_in_transaction, only: :show
private
def wrap_in_transaction
ActiveRecord::Base.transaction do
begin
yield
ensure
raise ActiveRecord::Rollback
end
end
end
end
لاحظ أنَّ المرشح "around" يلتف أيضًا على العرض. على وجه الخصوص، إذا كان المثال أعلاه مذكورًا في قاعدة البيانات (على سبيل المثال عبر نطاق)، فسيجري ذلك داخل المعاملة وبالتالي تقديم البيانات للمعاينة.
يمكنك عدم اختيار العائد (yield) وبناء الاستجابة بنفسك، وفي هذه الحالة لن ينفذ الإجراء.
طرق أخرى لاستخدام المرشحات
في حين أنَّ الطريقة الأكثر استخدامًا لاستخدام المرشحات هي عن طريق إنشاء توابع خاصة واستخدام action_*
لإضافتها، فهناك نوعان من طرق أخرى للقيام بنفس الشيء.
الأول هو استخدام كتلة مباشرة مع التوابع action_*
. تتلقى الكتلة وحدة التحكم كوسيطة. يمكن إعادة كتابة المرشح require_login
من الأعلى لاستخدام الكتلة.
class ApplicationController < ActionController::Base
before_action do |controller|
unless controller.send(:logged_in?)
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url
end
end
end
لاحظ أنَّ المرشح في هذه الحالة يستخدم الإرسال send
لأنَّ login_in?
تابع خاص ولا يشغل المرشح في نطاق وحدة التحكم. هذه ليست الطريقة الموصى بها لتطبيق هذا المرشح المحدد، ولكن في حالات أكثر بساطة قد يكون ذلك مفيدًا.
والطريقة الثانية هي استخدام صنف (في الواقع، أي كائن يستجيب للتوابع الصحيحة يمكن استخدامه) للتعامل مع المرشح. يفيد ذلك في الحالات الأكثر تعقيدًا ولا يمكن تنفيذها بطريقة مقروءة وقابلة لإعادة الاستخدام باستخدام التابعيين الأخريين. على سبيل المثال، يمكنك إعادة كتابة مرشح تسجيل الدخول مرة أخرى لاستخدام صنف:
class ApplicationController < ActionController::Base
before_action LoginFilter
end
class LoginFilter
def self.before(controller)
unless controller.send(:logged_in?)
controller.flash[:error] = "You must be logged in to access this section"
controller.redirect_to controller.new_login_url
end
end
end
مرة أخرى، هذا ليس مثالًا مثاليًا لهذا المرشح، لأنه لا يعمل في نطاق وحدة التحكم ولكنه يحصل على وحدة التحكم التي تمرر كوسيط. يجب أن يطبق صنف المرشح التابع مع الاسم نفسه كمرشح، لذلك، يجب على الفئة أن تقوم بتطبيق التابع before
لمرشح before_action
، وهكذا يجب على التابع around
أن يعيد عبر yield
لتنفيذ هذا الإجراء.
طلب حماية التزوير
التزوير في الطلب عبر الموقع (Cross-site request forgery) هو نوع من الهجوم حيث يخدع الموقع مستخدمًا في تقديم طلبات على موقع آخر، وربما إضافة أو تعديل أو حذف البيانات على هذا الموقع دون معرفة المستخدم أو إذنه.
الخطوة الأولى لتجنب هذا هو التأكد من أنه لا يمكن الوصول إلى جميع الإجراءات "المدمرة" (إنشاء وتحديث وتدمير) إلا بطلبات non-GET. إذا كنت تتبع اصطلاحات RESTful التي تفعلها بالفعل. ومع ذلك، لا يزال بإمكان الموقع الخبيث إرسال طلب non-GET إلى موقعك بسهولة تامة، وهنا يأتي طلب حماية التزوير. كما يشير الاسم، فإنه يحمي من الطلبات المزورة.
والطريقة هي إضافة رمز غير قابل للتخمين يعرفه الخادم فقط لكل طلب. بهذه الطريقة، إذا جاء طلب بدون الرمز الصحيح، فسيرفض الوصول.
إذا قمت بإنشاء نموذج مثل هذا:
<%= form_with model: @user, local: true do |form| %>
<%= form.text_field :username %>
<%= form.text_field :password %>
<% end %>
سترى كيف يضاف الرمز المميز كحقل مخفي:
<form accept-charset="UTF-8" action="/users/1" method="post">
<input type="hidden"
value="67250ab105eb5ad10851c00a5621854a23af5489"
name="authenticity_token"/>
<!-- fields -->
</form>
تضيف ريلز هذا الرمز المميز إلى كل نموذج يُنشَأ باستخدام مساعدي النموذج (form helpers)، لذلك معظم الوقت لا داعي للقلق بشأنه. إذا كنت تكتب نموذجًا يدويًا أو تحتاج إلى إضافة الرمز المميز لسبب آخر، فسيكون متاحًا من خلال التابع form_authenticity_token
:
ينشئ form_authenticity_token
رمز استيثاق صالح. وهذا مفيد في الأماكن التي لا تضيفها ريلز تلقائيًا، كما هو الحال في استدعاءات Ajax المخصصة.
يحتوي دليل الأمان على مزيد من المعلومات حول هذا الأمر والعديد من المشكلات الأخرى المتعلقة بالأمان والتي يجب أن تكون على دراية بها عند تطوير تطبيق ويب.
كائنات الطلب والاستجابة
يوجد في كل وحدة تحكم تابعين ملحقين يشيران إلى كائنات الطلب و الاستجابة المرتبطة بدورة الطلب قيد التنفيذ حاليًا. يحتوي تابع الطلب request
على نسخة من ActionDispatch::Request
ويقوم تابع الاستجابة response
بإرجاع كائن استجابة يمثل ما الذي سيرسل مرة أخرى إلى العميل.
كائن الطلب
يحتوي الكائن request
على الكثير من المعلومات المفيدة حول الطلب القادم من العميل. للحصول على قائمة كاملة بالتوابع المتاحة مع هذا الكائن، راجع توثيق Rails API وتوثيق Rack. من بين الخاصيات التي يمكنك الوصول إليها على هذا الكائن:
خاصية الكائن request
|
الغرض |
---|---|
host
|
اسم المضيف المُستخدَم لهذا الطلب. |
domain(n=2)
|
الأجزاء n الأولى من اسم المضيف، تبدأ من اليمين (TLD). |
format
|
نوع المحتوى المطلوب من قبل العميل. |
method
|
توابع HTTP المستخدمة للطلب. |
get?, post?, patch?, put?, delete?, head?
|
تعيد القيمة true إذا كانت طريقة HTTP هي GET / POST / PATCH / PUT / DELETE / HEAD. |
headers
|
تعيد التجزئة hash التي تحتوي على العناوين المرتبطة بالطلب. |
port
|
رقم المنفذ (عدد صحيح) المستخدم في الطلب. |
protocol
|
إرجاع سلسلة تحتوي على البروتوكول المستخدم بالإضافة إلى "://"، مثل "http://". |
query_string
|
جزء سلسلة الاستعلام من URL، أي كل شيء بعد الرمز "?". |
remote_ip
|
عنوان IP للعميل. |
url
|
عنوان URL بأكمله المُستخدَم في الطلب. |
معاملات المسار والاستعلام والطلب
تجمع ريلز كافة المعاملات المرسلة مع الطلب في التجزئة params
، سواء أُرسلَت كجزء من سلسلة الاستعلام أو نص في post. يحتوي الكائن request
على ثلاثة أدوات وصول تمنحك الوصول إلى هذه المعاملات اعتمادًا على مصدرها. تحتوي التجزئة query_parameters
على معاملات أُرسلت كجزء من سلسلة الاستعلام بينما تحتوي التجزئة request_parameters
على معاملات أُرسلت كجزء من جسم الطلبية post. بينما تحتوي التجزئة path_parameters
على معاملات جرى التعرف عليها عليها بواسطة التوجيه باعتبارها جزءًا من المسار المؤدي إلى وحدة التحكم هذه والإجراء المحدد.
كائن الإستجابة
لا يُستخدَم الكائن response
عادةً بشكل مباشر، ولكنه يُنشَأ أثناء تنفيذ الإجراء وعرض البيانات التي ترسل إلى المستخدم، ولكن أحيانًا - كما هو الحال في المرشح after - قد يكون من المفيد الوصول إلى الاستجابة مباشرةً. بعض من هذه التوابع ملحق بها أيضا ضابطات (setters)، مما يتيح لك ضبط وتغيير قيمها. للحصول على قائمة كاملة بالتوابع المتاحة، راجع توثيق Rails API وتوثيق Rack.
خاصية الكائن response
|
الغرض |
---|---|
body
|
هذه هي سلسلة البيانات التي يتم إرسالها مرة أخرى إلى العميل. هذا هو في الغالب HTML. |
status
|
رمز حالة HTTP للاستجابة، مثل 200 لطلب ناجح أو 404 لطلب لم يتم العثور عليه. |
location
|
عنوان URL الذي تتم إعادة توجيه العميل إليه، إن وجد. |
content_type
|
نوع محتوى الاستجابة. |
charset
|
ترميز المحارف المستخدمة للاستجابة. الترميز الافتراضي هو "utf-8". |
headers
|
العناوين التي تستخدم للاستجابة. |
ضبط عناوين مخصصة
إذا كنت ترغب في تعيين عناويين مخصصة للاستجابة، فإن response.headers
هو المكان المناسب للقيام بذلك. تعتبر الخاصية headers
عبارة عن جدول hash تعين أسماء العناوين إلى قيمها، وسوف تعين ريلز بعضها تلقائيًا. إذا كنت ترغب في إضافة عنوان أو تغييره، ما عليك سوى تعيينه عبر response.headers
على هذا النحو:
response.headers["Content-Type"] = "application/pdf"
ملاحظة: في الحالة المذكورة أعلاه، قد يكون من الأفضل استخدام الضابط content_type
مباشرةً.
HTTP استيثاق
تأتي ريلز مع آليتي استيثاق HTTP مدمجتين:
- استيثاق أساسي (Basic Authentication).
- استيثاق عبر قيم hash مختصرة (Digest Authentication).
استيثاق HTTP الأساسي
استيثاق HTTP الأساسي هو نظام استيثاق تدعمه غالبية المتصفحات وعملاء HTTP الآخرين. على سبيل المثال، ضع في الاعتبار قسم الإدارة الذي سيكون متاحًا فقط عن طريق إدخال اسم مستخدم وكلمة مرور في نافذة حوار HTTP الأساسية للمتصفح. يعد استخدام الاستيثاق المدمج سهلًا تمامًا ولا يتطلب منك سوى استخدام تابع واحد مثل http_basic_authenticate_with
.
class AdminsController < ApplicationController
http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end
مع هذا، يمكنك إنشاء وحدات تحكم ضمن مجال أسماء (namespaced controllers) ترث من AdminsController
. وبالتالي، سيُشغَّل المرشح لجميع الإجراءات في وحدات التحكم هذه، مع حمايتها باستخدام استيثاق HTTP الأساسي.
استيثاق طلبية HTTP عبر قيم hash مختصرة
يتفوق استيثاق طلبية HTTP عبر قيم hash مختصرة (HTTP digest authentication) على الاستيثاق الأساسي لأنه لا يتطلب من العميل إرسال كلمة مرور غير مشفرة عبر الشبكة (على الرغم من أن استيثاق HTTP الأساسي آمن عبر HTTPS). يعد استخدام استيثاق HTTP Digest مع ريلز أمرًا سهلاً للغاية ولا يتطلب سوى استخدام تابع واحد وهو Authenticate_or_request_with_http_digest
.
class AdminsController < ApplicationController
USERS = { "lifo" => "world" }
before_action :authenticate
private
def authenticate
authenticate_or_request_with_http_digest do |username|
USERS[username]
end
end
end
كما هو موضح في المثال أعلاه، تأخذ كتلة authenticate_or_request_with_http_digest
وسيطًا واحدًا فقط وهو اسم المستخدم. وتعيد الكتلة كلمة المرور. سيؤدي إعادة القيمة false
أو لا شيء من authenticate_or_request_with_http_digest
إلى حدوث فشل في الاستيثاق.
التدفق وتحميل الملفات
في بعض الأحيان قد ترغب في إرسال ملف إلى المستخدم بدلًا من عرض صفحة HTML. جميع وحدات التحكم في ريلز لها التابع send_data
و send_file
، وكلاهما سوف ترسل البيانات إلى العميل عبر مجرى (stream). إن send_file
هو تابع ملائم يسمح لك بتوفير اسم الملف على لتنزيله إلى حاسوب العميل وسوف تتدفق محتويات هذا الملف لتنزيله. لبدء تدفق البيانات إلى العميل، استخدم send_data
:
require "prawn"
class ClientsController < ApplicationController
# مع معلومات عن العميل وإعادته PDF توليد ملف
# PDF سيحصل المستخدم على الملف لتنزيله كملف
def download_pdf
client = Client.find(params[:id])
send_data generate_pdf(client),
filename: "#{client.name}.pdf",
type: "application/pdf"
end
private
def generate_pdf(client)
Prawn::Document.new do
text client.name, align: :center
text "Address: #{client.address}"
text "Email: #{client.email}"
end.render
end
end
سيقوم الإجراء download_pdf
في المثال أعلاه باستدعاء تابع خاص يقوم بالفعل بإنشاء مستند PDF وإعادتها كسلسلة. بعد ذلك ستبدأ هذه السلسلة إلى العميل بالتدفق لتنزيل الملف وسيُقترَح اسم الملف للمستخدم. في بعض الأحيان، عند تدفق الملفات إلى المستخدم، قد لا ترغب في تنزيل الملف. التقط صورًا، على سبيل المثال، لتضمينها في صفحات HTML. لإخبار المتصفح أنه ليس من المفترض تنزيل أحد الملفات، يمكنك تعيين الخيار :disposition
إلى القيمة "inline"
. القيمة المقابلة والافتراضية لهذا الخيار هي "attachment"
.
إرسال الملفات
إذا كنت ترغب في إرسال ملف موجود بالفعل على القرص، فاستخدم التابع send_file
.
class ClientsController < ApplicationController
# إرسال الملف الذي جرى توليده مسبقًا لتخزينه على جهاز العميل
def download_pdf
client = Client.find(params[:id])
send_file("#{Rails.root}/files/clients/#{client.id}.pdf",
filename: "#{client.name}.pdf",
type: "application/pdf")
end
end
سيؤدي ذلك إلى قراءة الملف وتسجيله بسرعة 4 كيلوبايت في ذلك الوقت، مع تجنب تحميل الملف بأكمله إلى الذاكرة مرة واحدة. يمكنك إيقاف تشغيل التدفق باستخدام الخيار :stream
أو ضبط حجم الكتلة باستخدام الخيار :buffer_size
.
إذا لم يحدَّد الخيار :type
، فسيُخمَّن من امتداد الملف المحدد في :filename
. إذا كان نوع المحتوى غير مسجل للإمتداد، فسيُستخدَم application/octet-stream.
تحذير: كن حذرًا عند استخدام البيانات القادمة من العميل (المعاملات، ملفات تعريف الارتباط، وما إلى ذلك) لتحديد موقع الملف على القرص، لأن ذلك يمثل خطرًا أمنيًا قد يسمح لشخص ما بالوصول إلى الملفات لا يفترض أن يصل إليها.
فائدة: لا يوصى بأن تقوم ببث ملفات ثابتة عبر ريلز إذا كان يمكنك بدلاً من ذلك الاحتفاظ بها في مجلد عام على خادم الويب الخاص بك. إنه أكثر فاعلية للسماح للمستخدم بتنزيل الملف مباشرة باستخدام خادم Apache أو خادم ويب آخر، مع الحفاظ على الطلب من دون داعٍ من خلال مكدس ريلز بأكمله.
تنزيلات RESTful
لمَّا كان send_data
يعمل على ما يرام، فإنَّه إذا كنت تقوم بإنشاء تطبيق RESTful مع وجود إجراءات منفصلة لتنزيل الملفات، فعادةً ما تكون غير ضرورية. في مصطلح REST، يمكن اعتبار ملف بصيغة PDF من المثال أعلاه مجرد تمثيل آخر لمورد العميل. توفر ريلز طريقةً سهلةً وميسرةً للغاية للقيام "بتنزيلات RESTful". في ما يلي كيفية إعادة كتابة المثال بحيث يكون تنزيل ملف PDF جزءًا من الإجراء show
، دون أي تدفق (streaming):
class ClientsController < ApplicationController
# HTML يمكن أن يرسل المستخدم طلبًا لاستقبال هذا المورد بهيئة صفحة
# PDF أو ملف
def show
@client = Client.find(params[:id])
respond_to do |format|
format.html
format.pdf { render pdf: generate_pdf(@client) }
end
end
end
لكي يعمل هذا المثال، يجب عليك إضافة نوع PDF MIME إلى ريلز. يمكن القيام بذلك عن طريق إضافة السطر التالي إلى ملف config/initializers/mime_types.rb:
Mime::Type.register "application/pdf", :pdf
لا يعاد تحميل ملفات التهيئة على كل طلب، لذا يجب عليك إعادة تشغيل الخادم حتى تُفعَّل التغييرات التي أُجريت. الآن يمكن للمستخدم طلب الحصول على نسخة PDF من عميل فقط عن طريق إضافة ".pdf" إلى عنوان URL:
GET /clients/1.pdf
تدفق مباشر للبيانات الإعتباطية
تسمح لك ريلز بتدفق أكثر من مجرد ملفات. في الواقع، يمكنك بث أي شيء تريده في الكائن response
. تسمح لك الوحدة ActionController::Live
بإنشاء اتصال دائم مع المتصفح. باستخدام هذه الوحدة، ستتمكن من إرسال بيانات اعتباطية إلى المتصفح في أوقات محددة.
دمج التدفق المباشر
تضمين ActionController::Live
داخل صنف وحدة التحكم الخاصة بك سيوفر كافة الإجراءات داخل وحدة التحكم للقدرة على تدفق البيانات. يمكنك الخلط في الوحدة بالشكل التالي:
class MyController < ActionController::Base
include ActionController::Live
def stream
response.headers['Content-Type'] = 'text/event-stream'
100.times {
response.stream.write "hello world\n"
sleep 1
}
ensure
response.stream.close
end
end
ستحافظ التعليمة البرمجية المذكورة أعلاه على اتصال مستمر مع المتصفح و إرسال 100 رسالة من "hello world \ n"، كل ثانية واحدة منفصلة.
هناك أمران يجب ملاحظتهما في المثال أعلاه. نحن بحاجة للتأكد من إغلاق تدفق الاستجابة. نسيان إغلاق التدفق سيترك المقبس مفتوحًا للأبد. يتعين علينا أيضًا تعيين نوع المحتوى على text/event-stream قبل أن نكتب إلى تدفق الاستجابة. هذا لأن العناوين لا يمكن كتابتها بعد أن نُفِّذَت الاستجابة (عندما تعيد ?response.committed
قيمة صحيحة)، والتي تحدث عند كتابة أو تنفيذ تدفق الاستجابة عبر write
أو commit
.
مثال عملي
لنفترض أنك كنت تصنع آلة كاريوكي (Karaoke machine) ويريد المستخدم الحصول على كلمات أغنية معينة. كل أغنية لها عدد معين من الخطوط وكل سطر يستغرق وقتًا num_beats
لإنهاء الغناء.
إذا أردنا إرجاع كلمات أغنية الكاريوكي (إرسال الخط فقط عندما ينتهي المغني من الخط السابق)، فيمكننا استخدام ActionController::Live
على النحو التالي:
class LyricsController < ActionController::Base
include ActionController::Live
def show
response.headers['Content-Type'] = 'text/event-stream'
song = Song.find(params[:id])
song.each do |line|
response.stream.write line.lyrics
sleep line.num_beats
end
ensure
response.stream.close
end
end
ترسل الشيفرة السابقة السطر التالي فقط بعد اكمال المغني للخط السابق.
اعتبارات التدفق
يعد بث البيانات الاعتباطية أداة قوية للغاية. كما هو موضح في الأمثلة السابقة، يمكنك اختيار متى وماذا يجب إرساله عبر تدفق الاستجابة. ومع ذلك، يجب عليك أيضًا ملاحظة الأشياء التالية:
- ينشئ كل تدفق استجابة مؤشر ترابط جديد وينسخ عبر متغيرات مؤشر الترابط المحلي من مؤشر الترابط الأصلي. وجود العديد من المتغيرات المحلية لمؤشر الترابط يمكن أن يؤثر سلبًا على الأداء. وبالمثل، فإن عددًا كبيرًا من سلاسل الرسائل يمكن أن يعوق الأداء أيضًا.
- سيؤدي الفشل في إغلاق مسار الاستجابة إلى إغلاق المقبس المطابق للأبد. تأكد من إغلاق الاتصال عندما تستخدم تدفق استجابة.
- خوادم WEBrick تخزِّن جميع الردود، وهكذا لن تعمل
ActionController::Live
. يجب عليك استخدام خادم ويب لا يقوم تلقائيًا بتخزين الاستجابات.
ترشيح السجل
تحتفظ ريلز بملف سجل (log) لكل بيئة في مجلد السجل. هذا مفيد للغاية عند تصحيح ما يحدث بالفعل في التطبيق الخاص بك، ولكن في تطبيق مباشر قد لا ترغب في تخزين كل جزء من المعلومات في ملف السجل.
ترشيح المعاملات
يمكنك ترشيح معاملات الطلب الحساسة من ملفات السجل الخاصة بك عن طريق إلحاقها بـ config.filter_parameters
في ضبط التطبيق. ستوضع علامة على هذه المعاملات بالشكل [FILTERED]
في السجل.
config.filter_parameters << :password
ملاحظة: ستُرشَح المعاملات المقدمة بواسطة التعبير النمطي المتطابق جزئيًّا. تضيف ريلز الخيار :password
افتراضيًّا في المهيئ (initializer) المناسب (initializers/filter_parameter_logging.rb) وتهتم بمعاملات التطبيق النموذجية password
و password_confirmation
.
ترشيح إعادة التوجيه
من المستحسن أحيانًا ترشيح بعض ملفات المواقع الحساسة التي يعيد تطبيقك التوجيه إليها من ملفات السجل. يمكنك القيام بذلك عن طريق استخدام خيار الضبط config.filter_redirect
:
config.filter_redirect << 's3.amazonaws.com'
يمكنك تعيينه إلى سلسلة نصية، أو تعبير نمطي، أو مصفوفة على حد سواء.
config.filter_redirect.concat ['s3.amazonaws.com', /private_path/]
عناوين URL المتطابقة يشار إليها بالعلامة "[FILTERED]"
.
معالجة الاستثناءات
على الأرجح سيحتوي التطبيق الخاص بك على أخطاء بطريقة أو بأخرى وسيُطلق استثناءات تحتاج إلى معالجة. على سبيل المثال، إذا اتبع المستخدم رابطًا لمورد لم يعد موجودًا في قاعدة البيانات، فسيلقي Active Record الاستثناء ActiveRecord::RecordNotFound
.
يعرض معالج الاستثناء الافتراضي في ريلز الرسالة "500 Server" لكافة الاستثناءات. إذا أنشئ الطلب محليًا، فسيعرض تتبع جيد وبعض المعلومات المضافة حتى تتمكن من معرفة الخطأ الذي حدث والتعامل معه. أمَّا إذا كان الطلب عن بُعد، فستعرض ريلز الرسالة "500 Server Error" البسيطة للمستخدم، أو الرسالة "404 Not Found" إذا كان هناك خطأ في التوجيه أو تعذر العثور على سجل ما. في بعض الأحيان قد ترغب في تخصيص كيفية اكتشاف هذه الأخطاء وكيفية عرضها للمستخدم. توجد عدة مستويات من معالجة الاستثناءات التي توفرها ريلز.
قوالب الخطأ 500 والخطأ 404 الافتراضية
بشكل افتراضي، سيعرض تطبيق الإنتاج رسالة الخطأ 404 أو 500، في بيئة التطوير تطلق كافة الاستثناءات غير المعالجة. تضمن هذه الرسائل في ملفات HTML الثابتة في المجلد العام ، في الملف 404.html و 500.html على التوالي. يمكنك تخصيص هذه الملفات لإضافة بعض المعلومات والتنسيقات الإضافية، ولكن تذكر أنها ملفات HTML ثابت؛ بمعنى أنه لا يمكنك استخدام ERB أو SCSS أو CoffeeScript أو مخططات معها.
الموجه Rescue_from
إذا كنت تريد القيام بشيء أكثر تفصيلًا عند اكتشاف الأخطاء، فيمكنك استخدام rescue_from
، الذي يعالج استثناءات من نوع معين (أو أنواع متعددة) في وحدة تحكم كاملة والفئات الفرعية الخاصة بها.
عند حدوث استثناء اكتُشِف بواسطة الموجه rescue_from
، يمرر كائن الاستثناء إلى المعالج. يمكن أن يكون المعالج تابع أو كائن Proc
مرر الى الخيار :with
. يمكنك أيضًا استخدام الحظر مباشرةً بدلًا من الكائن Proc
الصريح.
إليك كيف يمكنك استخدام rescue_from
لاعتراض جميع الأخطاء ActiveRecord::RecordNotFound
وفعل شيء ما معهم.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "404 Not Found", status: 404
end
end
بالطبع، هذا المثال هو أي شيء سوى أن يكون أكثر تفصيلًا إذ لا يحسن التعامل مع الاستثناءات الافتراضية على الإطلاق، ولكن بمجرد أن تتمكن من التقاط كل هذه الاستثناءات، فأنت حر في أن تفعل ما تريده معهم. على سبيل المثال، يمكنك إنشاء فئات استثناء مخصصة ستطلق عندما لا يكون لدى المستخدم حق الوصول إلى قسم معين من التطبيق الخاص بك:
class ApplicationController < ActionController::Base
rescue_from User::NotAuthorized, with: :user_not_authorized
private
def user_not_authorized
flash[:error] = "You don't have access to this section."
redirect_back(fallback_location: root_path)
end
end
class ClientsController < ApplicationController
# (clients) تحقق أنَّ المستخدم لديه الصلاحيات المناسبة للوصول إلى العملاء
before_action :check_authorization
# لا تهتم بأي شيء متعلق بالصلاحيات والرخص (actions) انتبه إلى أنَّ الإجراءات
def edit
@client = Client.find(params[:id])
end
private
# إن كان المستخدم غير موثوق ولا يملك الصلاحيات المناسبة، فسيرمى استثناء
def check_authorization
raise User::NotAuthorized unless current_user.admin?
end
end
تحذير: قد يتسبب استخدام rescue_from
مع Exception
أو StandardError
في حدوث آثار جانبية خطيرة حيث تمنع ريلز من التعامل مع الاستثناءات بشكل صحيح. على هذا النحو، لا ينصح باستعمال rescue_from
إلا إذا كان هناك سبب قوي لذلك.
ملاحظة: عند التشغيل في بيئة الإنتاج، تعرض كافة الأخطاء ActiveRecord::RecordNotFound
صفحة الخطأ 404. ما لم تكن بحاجة إلى سلوك مخصص، فأنت لا تحتاج إلى التعامل مع هذا الخطأ.
ملاحظة: بعض الاستثناءات المحددة هي فقط قابلة للانقاذ (rescuable) من الصنف ApplicationController
، كما أنها تُطلَق قبل أن تهيئ وحدة التحكم وأن ينفذ الإجراء.
فرض بروتوكول HTTPS
في وقت ما قد ترغب في فرض وحدة تحكم معينة على الوصول إليها فقط عبر بروتوكول HTTPS لأسباب أمنية. يمكنك استخدام طريقة force_ssl
في وحدة التحكم لفرض ما يلي:
class DinnerController
force_ssl
end
تمامًا مثل المرشح، يمكنك أيضًا تمرير الخيار :only
و :except
لفرض الاتصال الآمن فقط على إجراءات محددة:
class DinnerController
force_ssl only: :cheeseburger
# أو
force_ssl except: :cheeseburger
end
الرجاء ملاحظة أنه إذا وجدت نفسك تضيف force_ssl
إلى العديد من وحدات التحكم، فقد تحتاج إلى فرض استخدام التطبيق بأكمله على HTTPS بدلًا من ذلك. في هذه الحالة، يمكنك ضبط config.force_ssl
في ملف البيئة الخاص بك.