-
المساهمات
2851 -
تاريخ الانضمام
-
تاريخ آخر زيارة
-
عدد الأيام التي تصدر بها
38
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو Hassan Hedr
-
أهلًا عمرو، يمكنك التعليق أسفل الدرس الأخير ضمن الوحدة التي تتابعها ضمن الدورات وسيتابع معك المدربون ويجيبون على استفساراتك، بالتوفيق
-
أهلًا أسامة، رسالة الخطأ ستساعد في معرفة المشكلة هل يمكنك إرفاقها، شكرًا
-
يمكنك طرح سؤالك ضمن التعليقات أسفل كل درس ضمن الدورة التي تتابعها وسيجيبك المدربون على استفساراتك، يمكنك تقسيم الشيفرة المرفقة إلى أقسام وفهم كل منها لنفهم الشيفرة بأكملها، الدالة range تولد مصفوفة من الأعداد بطول محدد وهو 11 عدد ابتداءًا من الصفر، أي من صفر إلى 10: range(11) التعبير التالي هي حلقة for in تمر على كل عنصر من عناصر المصفوفة بعد الكلمة in وتسندها إلى المتغير i، وفي كل مرة سينفذ التعبير البرمجي قبل الكلمة for for i in range(11) أما التعبير البرمجي الممرر للحلقة for فهو يضرب قيمة i الحالية بنفسها i * i وتركيب كل العبارات تعبر عن نواتج مربع الأعداد من 0 حتى 10، وبما أن العبارة وضعت ضمن أقواس مربعة فتلك النواتج ستوضع ضمن مصفوفة جديدة أُسندت للمتغير squares، وبالتالي فقيمة ذلك المتغير هي مصفوفة من مربعات الأعداد من 0 حتى 10 squares = [...] والسطر الأخير يستدعي التابع print ويمرر له المتغير squares لطباعة قيمته عند تنفيذ البرنامج print(squares)
-
يحتوي كل برنامج على العديد من أنواع البيانات، إذ يجب أن تكون العمليات على البيانات بين نفس النوع من البيانات دومًا، مثل العمليات الرياضية على الأرقام، أو دمج السلاسل النصية معًا، ويمكن أن تكون هذه البيانات من مصادر خارجية، مثل لوحة المفاتيح أو استجابة لواجهة برمجة تطبيقات API أو قاعدة بيانات، إذ سنحتاج في هذه الحالة إلى التحويل بين أنواع البيانات قبل تنفيذ العمليات عليها، ولذلك الغرض توفر لغة روبي Ruby العديد من الطرق لتحويل القيم من نوع بيانات إلى آخر، لذلك سنتعرف في هذا المقال على طرق تحويل السلاسل النصية إلى أعداد، والكائنات إلى سلاسل نصية، والسلاسل النصية إلى مصفوفات، والتحويل بين السلاسل النصية والرموز. تحويل السلاسل النصية إلى أعداد توفر روبي التابعين to_i و to_f لتحويل السلاسل النصية إلى أعداد، إذ يحوّل to_i السلسلة النصية إلى عدد صحيح، ويحول التابع to_f السلسلة النصية إلى عدد عشري: "5".to_i # 5 "55.5".to_i # 55 "55.5".to_f # 55.5 لتوضيح ذلك، سننشئ برنامجًا صغيرًا يطلب من المستخدم إدخال عددين ويعرض مجموعهما، لذا ننشئ ملف جديد نسميه adder.rb ونضيف إليه الشيفرة التالية: print "What is the first number? " first_number = gets.chop print "What is the second number? " second_number = gets.chop sum = first_number + second_number print sum عند تشغيل البرنامج: $ ruby adder.rb سنحصل على الإجابة التالية: What is the first number? 5 What is the second number? 5 55 نلا حظ أن مجموع العدد 5 مع 5 هو 55 وهي نتيجة خاطئة، والسبب في ذلك أن القيم المُدخلة من لوحة المفاتيح لم ترسل العدد 5 بل أرسلت الحرف "5"، أي أن البرنامج ينظر إلى كل من العددين المدخلين على أنهما سلاسل نصية، وعند جمع السلسلتين النصين "5" و "5" سنحصل على السلسلة النصية الجديدة "55" وهي النتيجة التي ظهرت، ولحل المشكلة يجب تحويل المدخلات إلى أعداد أولًا قبل تنفيذ عملية الجمع، ويمكننا ذلك باستخدام التابع to_f على النحو التالي: print "What is the first number? " first_number = gets.chop print "What is the second number? " second_number = gets.chop # convert strings to numbers first_number = first_number.to_f second_number = second_number.to_f sum = first_number + second_number print sum ننفذ البرنامج مجددًا: $ ruby adder.rb لنلاحظ جمع الأعداد حسابيًا وإظهار النتيجة الصحيحة: What is the first number? 5 What is the second number? 5 10.0 عند محاولة تحويل سلسلة نصية غير قابلة للتحويل إلى عدد باستخدام أحد التابعين to_i و to_f على النحو التالي: "123-abc".to_i سيكون الخرج: 123 نلاحظ تحويل السلسلة النصية "123-abc" إلى العدد الصحيح 123 باستخدام التابع to_i، إذ يتوقف ذلك التابع عندما يصل إلى أول محرف غير رقمي ويعيد النتيجة. يستغل مطورو الويب هذه الخاصية في روبي لإنشاء روابط URL مثل 15-sammy-shark، إذ يدل الرقم 15 في ذلك النص على معرف داخلي للبحث ضمن سجل قاعدة البيانات، أما القسمsammy-shark هو وصف نصي للرابط ليسهل قراءته، وعند تحويل القيمة 15-sammy-shark إلى عدد صحيح باستخدام التابع to_i، سيتجاهل التابع الجزء النصي -sammy-shark ويعيد العدد الصحيح 15 الذي يمكن استخدامه للبحث ضمن سجل قاعدة البيانات، وفيما يلي مثال آخر عن تحويل سلسلة نصية غير عددية: "abc".to_i سيكون الخرج: 0 نلاحظ أن التابع to_i يعيد القيمة 0، إذ لا يمكن تحويل أي من المحارف في تلك السلسلة النصية إلى عدد صحيح، وقد يسبب ذلك مشاكل فإذا أدخل المستخدم بإدخال القيمة "abc" ضمن برنامج ما وحولنا هذه القيمة إلى عدد صحيح ثم أجرينا عملية القسمة على ذلك العدد سيحدث خطأ لعدم إمكانية القسمة على الصفر، ولحل تلك المشكلة توفّر روبي طريقةً أخرى لتحويل البيانات إلى أعداد باستخدام أحد التابعينInteger أوFloat للتحويل: Integer("123") والخرج: 123 عند محاولة تحويل قيمة غير صالحة للتحويل باستخدام التابع Integer سيرمي خطأ في حال لم تكن كل السلسلة النصية قابلة للتحويل: Integer("123abc") سيكون الخرج في هذه الحالة على النحو التالي: ArgumentError: invalid value for Integer(): "123abc" نستفيد من ذلك الخطأ لإظهار رسالة للمستخدم مثلًا نطلب منه إدخال قيمة عددية صحيحة، مما يضمن سلامة البيانات المُدخلة وأن القيمة التي أدخلها المستخدم هي المقصودة. سنتعرف في الفقرة القادمة على طريقة تحويل أنواع أخرى من البيانات إلى سلاسل نصية. تحويل البيانات إلى سلاسل نصية توفر لغة روبي التابع to_s لتحويل أي نوع من البيانات إلى سلسلة نصية: 25.to_s # "25" (25.5).to_s # "25.5" ["Sammy", "Shark"].to_s # "[\"Sammy\", \"Shark\"]" نحتاج غالبًا قبل عرض البيانات للمستخدم إلى تحويل البيانات إلى سلاسل نصية، ففي المثال التالي سنحاول طباعة قيمة من نوع سلسلة نصية وقيمة عددية. نبدأ بإنشاء ملف بالاسم calories.rb يحتوي على التالي: user = "Sammy" calories = 100 print "Congratulations, " + user + "! You just burned " + calories + " calories during this workout." أضفنا قيم المتغيرات في المثال السابق يدويًا، ولكن عادة ما سنستقبل تلك القيم من مصدر خارجي مثل مدخلات المستخدم أو من قاعدة البيانات أو غيرها، والآن ننفذ البرنامج: $ ruby calories.rb ليظهر لنا رسالة الخطأ التالية: ... TypeError: no implicit conversion of Integer into String (TypeError) لا يمكن إضافة المتغير calories إلى باقي النص الذي نحاول طباعته لأنه يحتوي على قيمة من نوع عدد صحيح لا يمكن تحويلها إلى سلسلة نصية بإضافة علامات تنصيص حولها بل باستخدام التابع to_s قبل دمجها مع بقية النص المطبوع. لحل المشكلة: نعدّل السطر الذي يحتوي على النتيجة ونحول قيمة المتغير calories إلى سلسلة نصية باستخدام التابع to_s على النحو التالي: user = "Sammy" calories = 100 print "Congratulations, " + user + "! You just burned " + calories.to_s + " calories during this workout." نُنفذ البرنامج مجددًا ونلاحظ اختفاء الخطأ السابقة وظهور النتيجة المطلوبة: Congratulations, Sammy! You just burned 100 calories during this workout. يمكن أيضًا استخدام خاصية استيفاء النص interpolation المتوفرة في لغة روبي، والتي تحول الكائنات إلى سلاسل نصية تلقائيًا دون الحاجة لأي عمليات تحويل إضافية، لنحاول استخدامها في المثال السابق على النحو التالي: print "Congratulations, #{user}! You just burned #{calories} calories during this workout." ننفذ البرنامج مرةً أخرى ونلاحظ ظهور النتيجة نفسها، إذ توفر جميع الكائنات في لغة روبي التابع to_s الذي يحول الكائن إلى سلسلة نصية بطريقة موحدة، وفي حال أردنا التحويل بطريقة مختلفة يمكننا كتابة شيفرة مخصصة لذلك. توفر جميع الكائنات في لغة روبي التابع inspect الذي يفيد خلال مرحلة التطوير في عملية تنقيح الأخطاء، إذ يعيد ذلك التابع نفس النتيجة التي يعيدها التابع to_s مع بيانات أكثر تفصيلًا عن الكائن، لذا يقتصر استخدام ذلك التابع في مرحلة التطوير فقط، ويمكن استخدامه مع التابع puts لعرض قيم المتغيرات أثناء تطوير البرنامج. سنتعرف في الفقرة القادمة على طريقة تحويل سلسلة نصية إلى مصفوفة. تحويل السلاسل النصية إلى مصفوفات يمكن تحويل أي سلسلة نصية إلى مصفوفة باستخدام التابع split على النحو التالي: "one two three".split ويكون الخرج على النحو التالي: ["one", "two", "three"] يمكن تحديد المحرف الذي نريد استخدامه مثل محدد لفصل السلسلة النصية إلى عناصر بتمريره بمثابة معامل إلى التابع split. سنحوّل في المثال التالي سلسلة نصية تحتوي على قيم مفصولة بفواصل إلى مصفوفة ثم سنرتبها ونطبع قيمة كل عنصر منها على الشاشة، ونبدأ بإنشاء ملف بالاسم "data_import.rb" يحتوي على الشيفرة التالية: data = "Tiger,Great White,Hammerhead,Whale,Bullhead" # Convert data to an array by splitting on commas sharks = data.split(",") # Sort the sharks alphabetically sharks = sharks.sort! # Print out the sharks by iterating through the array sharks.each{|shark| puts shark } ننفذ البرنامج: $ ruby data_import.rb لنحصل على الخرج التالي: Bullhead Great White Hammerhead Tiger Whale نلاحظ مدى قوة المصفوفات في لغة روبي والاستخدامات المتعددة لها. سنتعلم في الفقرة التالية طريقة التحويل بين السلاسل النصية والرموز. التحويل بين السلاسل النصية والرموز يمكن تحويل رمز إلى سلسلة نصية لنتمكن من طباعته، وقد نحتاج أحيانًا لتحويل سلسلة نصية إلى رمز لاستخدامه للبحث ضمن جدول Hash، إذ يمكن للتابع to_s تحويل الرموز إلى سلاسل نصية مباشرةً: :language.to_s ويكون الخرج: "language" يمكننا التعديل على الشكل النهائي للسلسلة النصية المُعبرة عن الرمز، ففي المثال التالي سنحول الرمز first_name: إلى السلسلة النصية الأوضح والأسهل بالقراءة "First name" كالتالي: string = :first_name.to_s # replace underscore with a space and capitalize string = string.gsub("_"," ").capitalize يمكن تحويل السلاسل النصية إلى رموز باستخدام التابع to_sym كما يلي: "first_name".to_sym وبذلك يكون الخرج: :first_name لتحويل السلسلة النصية "First name" إلى الرمز first_name: مجددًا، يجب أولًا تحويل جميع الأحرف إلى الحالة الصغيرة واستبدال المسافات بشرطات سفلية على النحو التالي: string = "First name" # replace spaces with underscores and convert to lowercase string = string.gsub(" ","_").downcase # Convert to symbol symbol = string.to_sym نحتاج كثيرًا للتحويل بين الرموز والسلاسل النصية، سواءٌ لعرض رمز ما بشكلٍ سهل القراءة للمستخدم أو لاستخدام السلسلة النصية للبحث عن مفتاح محدد ضمن جدول Hash الذي يستخدم الرموز من أجل مفاتيحه. الخاتمة تعرفنا في هذا المقال على طرق تحويل بعض أنواع البيانات الأساسية إلى أنواع بيانات أخرى باستخدام التوابع التي توفرها لغة روبي ضمن الكائنات، مثل تحويل الأرقام إلى سلاسل نصية، والسلاسل النصية إلى مصفوفات، والتحويل بين الرموز والسلاسل النصية، إذ من المهم التعرف على طرق التحويل تلك للتعامل مع أنواع البيانات المختلفة وتنفيذ العمليات عليها. ترجمة -وبتصرف- للمقال How To Convert Data Types in Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: توابع المصفوفات في لغة روبي السلاسل النصية في لغة روبي دليلك الشامل إلى أنواع البيانات
-
تسمح المصفوفات arrays بتمثيل قائمة من البيانات معًا وتخزينها ضمن متغير واحد لنتمكن من إجراء العمليات المختلفة على تلك البيانات، مثل فرزها أو إزالة العناصر المكررة منها، أو عكس ترتيبها، أو استخراج أجزاء من المصفوفة، أو البحث ضمنها عن بيانات محددة، ويمكننا أيضًا تحويل المصفوفة إلى سلسلة نصية أو إلى مصفوفة بيانات أخرى أو دمج عناصر المصفوفة معًا في قيمة واحدة. سنتعرف في هذا المقال على بعض التوابع التي توفرها لغة روبي للتعامل مع البيانات ضمن المصفوفات. سنلاحظ انتهاء أسماء بعض تلك التوابع بعلامة تعجب !، مما يشير أن هذا التابع سيعدل على قيمة المصفوفة الأصلية وقد يرمي استثناءات، ويوجد غالبًا تابع مقابل بنفس الاسم لمعظم التوابع الموجودة ضمن المصفوفات ولكن ينتهي بهذه اللاحقة. يوجد كذلك توابع أخرى تنتهي بعلامة استفهام ? وهي توابع تعيد قيمة بوليانية منطقية تشير، ويُعد هذا الأسلوب شائعًا في تسمية التوابع ضمن روبي، وسنبدأ بالتعرف على توابع المصفوفات بدايةً من خلال عدة طرق للوصول إلى العناصر ضمن المصفوفة. الوصول إلى عنصر من المصفوفة تعرفنا في المقال السابق على طريقة الوصول لعنصر محدد ضمن المصفوفة باستخدام رقم الفهرس الخاص به والذي يبدأ من الصفر على النحو التالي: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sharks[0] # "Tiger" sharks[1] # "Great White" sharks[-1] # "Angel" وتعرفنا أيضًا على كل من التابعين first و last واللذان يمكن استخدامهما للحصول على أول وآخر عنصر من المصفوفة: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sharks.first # "Tiger" sharks.last # "Angel" وعند الوصول لعنصر غير موجود ضمن المصفوفة، سنحصل على القيمة nil، وإذا أردنا أن ينتج عن تلك العملية خطأ فيمكن استخدام التابع fetch: sharks.fetch(42) ليكون الخرج على النحو التالي: IndexError: index 42 outside of array bounds: -4...4 ويمكن تحديد قيمة افتراضية للعناصر غير الموجودة بدلًا من رمي خطأ، مثل تمرير تلك القيمة الافتراضية مثل وسيط ثانٍ للتابع fetch: sharks.fetch(42, "Nope") # "Nope" سنتعرف في الفقرة التالية على طريقة الحصول على عدة عناصر من المصفوفة معًا. استخراج عدة عناصر من المصفوفة قد نحتاج أحيانًا للحصول على عدة عناصر من المصفوفة معًا بدلًا من عنصر واحد فقط، ويمكننا ذلك عبر تحديد رقم الفهرس لأول عنصر نريده من المصفوفة، ثم تمرير عدد العناصر التي نريد استخراجها بدءًا من ذلك العنصر، لتُنشأ مصفوفةٌ جديدةٌ تحتوي على تلك العناصر المحددة، فمثلًا يمكن استخراج العنصرين المتوسطين من المصفوفة sharks على النحو التالي: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sharks[1,2] # ["Great White", "Hammerhead"] نبدأ من الفهرس رقم 1 الذي يحتوي على قيمة "Great White" ونحدد بعدها عدد العناصر التي نريد استخراجها وهو 2، وبذلك تُنشأ مصفوفة جديدة تحتوي على "Great White" و "Hammerhead". يمكن استخدام التابع slice لإجراء نفس العملية: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sharks.slice(1,2) # ["Great White", "Hammerhead"] يعيد التابع slice مصفوفةً جديدةً دون التعديل على المصفوفة الأصلية، ولكن إذا استخدمنا التابع !slice ستتغير قيمة المصفوفة الأصلية. يمكن أيضًا استخدام التابع take لاستخراج عدد محدد من العناصر بدءًا من بداية المصفوفة: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sharks.take(2) # ["Tiger", "Great White"] قد نحتاج لاستخراج عنصر عشوائي من المصفوفة بدلًا من قيمة محددة، وسنتعرف على طريقة تنفيذ ذلك في الفقرة التالية. الحصول على عنصر عشوائي من المصفوفة قد نحتاج في بعض الحالات لاستخراج قيمة عشوائية لا على التعيين من مصفوفة ما، خصوصًا عند تطوير بعض الألعاب التي تعتمد على الحظ، إذ يمكننا تنفيذ ذلك بتخزين جميع الخيارات الممكنة ضمن مصفوفة واختيار إحداها بتوليد رقم فهرس عشوائي بين الرقم 0 ورقم آخر فهرس لآخر عنصر من المصفوفة. تتيح لك روبي طريقةً أسهل عبر التابع sample، الذي يعيد عنصرًا عشوائيًا من المصفوفة مباشرةً على النحو التالي: answers = ["Yes", "No", "Maybe", "Ask again later"] print answers.sample ويكون الخرج: Maybe يقبل التابع sample عددًا مثل وسيط أول، ويعيد مصفوفةً تحتوي على عدد محدد من العناصر العشوائية، فإذا كنا بحاجة لأكثر من عنصر عشوائي، يمكننا تمرير العدد المطلوب لذلك التابع كما يلي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] sample = sharks.sample(2) print sample ليكون الخرج على النحو التالي: ["Whale", "Great White"] سنتعرف في الفقرة التالية على طرق البحث عن عناصر محددة داخل مصفوفة. البحث وتصفية عناصر المصفوفة يمكننا البحث عن عناصر محددة داخل مصفوفة بالمرور على عناصر تلك المصفوفة واحدًا تلو الآخر وفحص كل عنصر منها إلى أن نجد العنصر المطلوب، ولكن روبي توفر عدة توابع تُسهل عملية البحث تلك ضمن المصفوفات، إذ يمكن مثلًا التحقق ما إذا كان العنصر موجودًا ضمن المصفوفة باستخدام التابع ?include، الذي يعيد القيمة true في حال كان العنصر المحدد موجودًا: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] sharks.include? "Tiger" # true ["a", "b", "c"].include? 2 # false إذ يبحث التابع ضمن المصفوفة عن العنصر الذي يطابق القيمة المُمررة له تمامًا، أي لا يمكننا مثلًا البحث عن أجزاء من سلسلة نصية على النحو التالي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] sharks.include? "Tiger" # true sharks.include? "tiger" # false sharks.include? "ti" # false يمكن حل المشكلة باستخدام التابع find، الذي يعيد أول عنصر يطابق الشرط المحدد داخل المصفوفة، فمثلًا للبحث عن أول عنصر يحتوي على الحرف a ضمن مصفوفة sharks، يمكن استخدام التابع each للمرور على كل عنصر بالترتيب والتوقف عند العثور على العنصر الأول المطابق لهذا الشرط كما يلي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] result = nil sharks.each do |shark| if sharks.include? "a" result = shark break end end أو استخدام التابع find بدلًا من ذلك لتنفيذ نفس العملية: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] result = sharks.find {|item| item.include?("a")} print result ويكون الخرج: Hammerhead ينفذ التابع find التعبير البرمجي الممرر له على كل عنصر داخل المصفوفة، وفي حال كانت القيمة التي يعيدها ذلك التعبير البرمجي الممرر القيمة true، يوقف التابع find عملية المرور ويعيد تلك القيمة مباشرةً، وإذا لم يُعثر على أي عنصر يطابق الشرط المحدد يعيد القيمة nil. يعمل التابع select بطريقة مشابهة للتابع find ولكنه سيُنشئ مصفوفة جديدة تحتوي على جميع العناصر التي تطابق الشرط المحدد بدلًا من قيمة واحدة فقط على النحو التالي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] results = sharks.select {|item| item.include?("a")} print results وتكون النتيجة على النحو التالي: ["Hammerhead", "Great White", "Whale"] يعيد التابع reject مصفوفةً جديدةً تحتوي على العناصر التي لا تطابق الشرط المحدد، ويمكن استخدامه لتصفية وإزالة العناصر التي لا نرغب بها من المصفوفة، فمثلًا يمكننا إزالة جميع العناصر التي تحتوي على الحرف a من المصفوفة كما يلي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] results = sharks.reject {|item| item.include?("a")} print results ويكون الخرج: ["Tiger"] يعيد كلًا من التابعين select و reject مصفوفةً جديدةً دون تغيير قيمة المصفوفة الأصلية، ويمكن استخدام التابعين المقابلين !select و !reject في حال أردنا التعديل على قيمة المصفوفة الأصلية؛ أما التابع find_all فهو بديل للتابع selectولكن الفرق بأنه لا يوجد تابع مقابل له للتعديل على قيمة المصفوفة. سنتعرف في الفقرة التالية على طرق ترتيب عناصر المصفوفة. ترتيب عناصر المصفوفة نحتاج في كثير من الأحيان لترتيب بعض البيانات، مثل ترتيب قائمة من الأسماء أبجديًا، أو ترتيب بعض الأرقام من الأصغر إلى الأكبر، ومن التوابع التي تتعامل مع ترتيب العناصر ضمن المصفوفة هو التابع reverse، الذي يعكس ترتيب العناصر؛ فإذا كانت لدينا قائمة مرتبة من بيانات يمكن استخدام ذلك التابع بمثابة طريقة سريعة لعكس ترتيبها على النحو التالي: sharks = ["Angel", "Great White", "Hammerhead", "Tiger"] reversed_sharks = sharks.reverse print reversed_sharks ويكون الخرج: ["Tiger", "Hammerhead", "Great White", "Angel"] يعيد التابع reverse مصفوفةً جديدةً دون تغيير قيمة المصفوفة الأصلية، ويمكن استخدام التابع !reverse في حال أردنا تغيير قيمة المصفوفة الأصلية. لا يكفي عكس ترتيب عناصر المصفوفة، إذ نحتاج معظم الأحيان لترتيب عناصر غير مرتبة، ويمكن لذلك استخدام التابع sort لترتيب العناصر ضمن المصفوفة بأي طريقة نريدها، فإذا كانت العناصر ضمن المصفوفة قيم بسيطة مثل السلاسل النصية أو الأرقام، فسيرتب التابع sort تلك العناصر مباشرةً: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sorted_sharks = sharks.sort print sorted_sharks ويعطي الخرج التالي: ["Angel", "Great White", "Hammerhead", "Tiger"] لترتيب البيانات بطريقة مختلفة يجب أن نخبر التابع sort بطريقة فعل ذلك، إذ يمكن تمرير كتلة برمجية للتابع sort الذي سيمرِّر لها عناصر المصفوفة لمقارنتها ويمكن ذلك باستخدام معامل المقارنة <=>، إليك مثالًا عن ذلك: 1 <=> 2 # -1 2 <=> 2 # 0 2 <=> 1 # 1 يقبل التابع sort في لغة روبي كتلةً برمجيةً يجب أن تعيد إحدى القيم 1- أو 0 أو 1 للتعبير عما إذا كان العنصر الأول يسبق أو يساوي أو يلي العنصر الثاني بالترتيب، وبناءً على تلك المقارنة تُرتب عناصر المصفوفة، وفي ما يلي مقارنة لقيم عناصر المصفوفة وترتيبها تصاعديًا: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sorted_sharks = sharks.sort{|a,b| a <=> b } print sorted_sharks يُمثل المتغيران a و b عنصرين من المصفوفة يُقارن بينهما حاليًا، ونتيجة ترتيب المصفوفة هي: ["Angel", "Great White", "Hammerhead", "Tiger"] يمكن عكس ترتيب العناصر ضمن عبارة المقارنة بينهما لترتيب العناصر بترتيب عكسي على النحو التالي: sharks = ["Tiger", "Great White", "Hammerhead", "Angel"] sorted_sharks = sharks.sort{|a,b| b <=> a } print sorted_sharks ليكون الخرج: ["Tiger", "Hammerhead", "Great White", "Angel"] كما لاحظنا، يمكن باستخدام التابع sort ترتيب المصفوفات التي تحتوي على أنواع بسيطة من البيانات، مثل الأعداد الصحيحة، أو العشرية، أو السلاسل النصية، ولكن عندما تحتوي المصفوفة على كائنات أكثر تعقيدًا، سيكون عليك فعل المزيد. يوضح المثال التالي مصفوفة من قيم من نوع hash، وكل hash يمثّل سمك قرش shark: sharks = [ {name: "Hammerhead"}, {name: "Great white"}, {name: "Angel"} ] سيفشل ترتيبها مباشرةً باستخدام sort: sharks.sort وعند استدعائه على تلك المصفوفة تظهر الرسالة التالية: ArgumentError: comparison of Hash with Hash failed ولمقارنة تلك القيم يجب أن نخبر التابع sort بطريقة المقارنة بينها. لنرتب تلك الكائنات أبجديًا بحسب خاصية الاسمname: ضمنها على النحو التالي: sorted_sharks.sort{|a, b| a[:name] <=> b[:name]} print sorted_sharks ليكون الخرج: [{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}] يمكن عند ترتيب مصفوفة من الكائنات استخدام التابع sort_by بدلًا من sort، الذي يستخدم خوارزمية أكثر كفاءة في الترتيب، ويقبل التابع sort_by كتلة برمجية يُمرر إليها وسيط واحد فقط وهو العنصر الحالي في المصفوفة: sharks = [ {name: "Hammerhead"}, {name: "Great white"}, {name: "Angel"} ] sorted_sharks = sharks.sort_by{|shark| shark[:name] } print sorted_sharks يكون الخرج هنا: [{:name=>"Angel"}, {:name=>"Great white"}, {:name=>"Hammerhead"}] يُطبق التابع sort_by خوارزمية ترتيب تدعى تحويل شوارتزيان Schwartzian transform، التي تُستخدم لمقارنة الكائنات بناءً على قيمة محددة منها، وهي خوارزمية أكثر كفاءة من خوارزمية الترتيب ضمن التابع sort؛ إذ يعيد كلًا من التابعين sort و sort_by مصفوفات جديدة دون تغيير قيمة المصفوفة الأصلية، وللتعديل على قيمة المصفوفة الأصلية نستخدم التابعين المقابلين لهما!sort و !sort_by، وقد نحتاج أحيانًا لحذف العناصر المكررة ضمن المصفوفة، وهذا ما سنتعرف عليه في الفقرة التالية. إزالة العناصر المكررة من المصفوفة قد تحتوي المصفوفات على بعض العناصر المكررة ضمنها، ولإزالة ذلك التكرار يمكن المرور على المصفوفة وإزالة التكرار منها يدويًا، أو يمكن استخدام التابع uniq، الذي يعيد مصفوفةً جديدةً تحتوي قيم المصفوفة نفسها ولكن دون التكرار: [1,2,3,4,1,5,3].uniq # [1,2,3,4,5] سنحاول في المثال التالي دمج مجموعتين من البيانات وسنعالج مشكلة تكرار بعض العناصر في المصفوفة الناتجة: sharks = ["Tiger", "Great White"] new_sharks = ["Tiger", "Hammerhead"] يمكن الدمج بينها باستخدام معامل الجمع + لنحصل على عنصر مكرر في المصفوفة الناتجة: sharks + new_sharks # ["Tiger", "Great White", "Tiger", "Hammerhead"] يمكن استخدام التابع uniq لإزالة التكرار في المصفوفة الناتجة، ولكن بدلًا من ذلك يمكن تجنب ذلك التكرار من الأساس باستخدام معامل الأنبوب | الذي يدمج المصفوفتين دون تكرار العناصر المشتركة بينهما بدلًا من معامل الجمع + وذلك على النحو التالي: sharks | new_sharks # ["Tiger", "Great White", "Hammerhead"] يمكن في روبي طرح المصفوفات من بعضها باستخدام معامل الطرح - للحصول على القيم غير المكررة فقط بين كلا المصفوفتين كما يلي: sharks = ["Tiger", "Great White"] new_sharks = ["Tiger", "Hammerhead"] sharks - new_sharks # ["Great White"] سنتعرف في الفقرة التالية على طرق التعديل على عناصر المصفوفة. تعديل عناصر المصفوفة يمكن استخدام التابع map أو التسمية البديلة له collect لتحويل محتويات المصفوفة بتطبيق عملية ما على كل عنصر في المصفوفة، فمثلًا يمكن تنفيذ عمليات حسابية على كل عنصر في المصفوفة وإنشاء مصفوفة جديدة تحتوي على قيمة تلك العملية الحسابية لكل عنصر منها: numbers = [2,4,6,8] # square each number squared_numbers = numbers.map {|number| number * number} print squared_numbers المتغير squared_numbers هو مصفوفة تحتوي على قيمة مربع كل عدد من المصفوفة الأصلية: [4, 16, 36, 64] يُستخدم التابع map غالبًا في تطبيقات الويب لتحويل عناصر من المصفوفة إلى عناصر HTML لعرضها ضمن قائمة منسدلة في الصفحة على النحو التالي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] options = sharks.map {|shark| "<option>#{shark}</option>"} print options تحتوي المصفوفة الجديدة options على كل عنصر من المصفوفة الأصلية محاطًا بالوسم <option></option>: ["<option>Hammerhead</option>", "<option>Great White</option>", "<option>Tiger</option>", "<option>Whale</option>"] يعيد التابع map مصفوفةً بالقيم الجديدة دون التعديل على المصفوفة الأصلية، ويمكن باستخدام التابع !map تعديل قيمة المصفوفة الأصلية، ويمكن استخدام التسمية البديلة للتابع collect بنفس الطريقة، ويُفضّل اعتماد أحد هذين التابعين لتوحيد الاستخدام ضمن الشيفرة البرمجية، وبما أن التابع map يعيد مصفوفةً جديدة فيمكن تحويل أو تعديل عناصر تلك المصفوفة الجديدة مجددًا أو تحويلها مثلًا إلى سلسلة نصية، وسنتعرف على ذلك في الفقرة التالية. تحويل المصفوفة إلى سلسلة نصية تحتوي جميع الكائنات في لغة روبي على التابع to_s والذي يحوّل أي كائن إلى سلسلة نصية، وهذا ما يستخدمه التابع print داخليًا لطباعة الكائنات المٌمررة له على الشاشة. لنختبر ذلك على المصفوفة التالية: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] ينتج عن استدعاء التابع to_s على تلك المصفوفة السلسلة النصية التالية: "[\"Hammerhead\", \"Great White\", \"Tiger\", \"Whale\"]" قد تفيدنا تلك العملية في معاينة قيم المتغيرات أثناء تطوير البرامج. يوجد أيضًا التابع join، الذي يحوّل المصفوفة إلى سلسلة نصية بطريقة أخرى عبر دمج تلك العناصر بجانب بعضها بعضًا، ويقبل وسيطًا هو سلسلة نصية ستفصل بين تلك العناصر مثل المسافة الفارغة. لنختبر ذلك بتحويل المصفوفة السابقة إلى سلسلة نصية على النحو التالي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] result = sharks.join(" ") print result لتكون النتيجة: Hammerhead Great White Tiger Whale يمكن بدلًا من المسافة الفارغة فصل القيم بفاصلة ومسافة بين العناصر بتمريرها للتابع كما يلي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] result = sharks.join(", ") print result ويكون الخرج في هذه الحالة: Hammerhead, Great White, Tiger, Whale وإذا لم نمرر أي وسيط للتابع join، سيعيد سلسلةً نصيةً تحتوي على العناصر الموجودة في المصفوفة دون فواصل بينها: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] result = sharks.join print result وتكون النتيجة على النحو التالي: HammerheadGreat WhiteTigerWhale يمكن استخدام التابع join مع التابع map لتحويل مصفوفة من البيانات إلى نتيجة يمكن عرضها على الشاشة، إذ يحوّل التابع map كل عنصر من المصفوفة إلى الشكل الجديد، ثم يدمج التابع join تلك القيم في سلسلة نصية يمكن طباعتها على الشاشة، إذ يمكننا مثلًا تحويل العناصر إلى عناصر HTML كما فعلنا سابقًا ثم دمجها باستخدام التابع join في سلسلة نصية يفصل بينها أسطر جديدة: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] options = sharks.map {|shark| "<option>#{shark}</option>"} output = options.join("\n") print output ليعطي الخرج التالي: <option>Hammerhead</option> <option>Great White</option> <option>Tiger</option> <option>Whale</option> أحيانًا نحتاج لحساب قيمة إجمالية من عناصر المصفوفة أو تنفيذ عملية ما عليها ينتج منها قيمة واحدة، وسنتعرف على طريقة ذلك في الفقرة التالية. تقليص المصفوفة إلى قيمة واحدة يمكن حساب قيمة معينة من عناصر المصفوفة بالمرور على كل عنصر منها باستخدام التابع each ودمجه في النتيجة النهائية، مثل حساب المجموع الكلي للأرقام ضمن المصفوفة على النحو التالي: result = 0 [1, 2, 3].each {|num| result += num} print result سيكون الخرج: 6 يمكننا بدلًا من ذلك استخدام التابع reduce، الذي يُنفذ عمليةً معينة على كل عنصر من عناصر المصفوفة مع تمرير قيمة الدمج الحالية مع العنصر الحالي إلى الكتلة البرمجية المٌمررة له، إذ يقبل التابع reduce وسيطًا وهو القيمة الابتدائية للنتيجة ويستخدم الكتلة البرمجة المُمررة له والتي سيُمرر لها متغيرين هما قيمة الدمج الحالية والعنصر الحالي، ويمكن داخل الكتلة البرمجية تحديد طريقة إضافة العنصر الحالي إلى النتيجة النهائية، فمثلًا لجمع عناصر المصفوفة يمكن تمرير القيمة 0 مثل قيمة ابتدائية للنتيجة ثم إضافة قيمة كل عنصر من المصفوفة إلى النتيجة المُمررة داخل الكتلة البرمجية على النحو التالي: output = [1,2,3].reduce(0) {|result, current| result += current } print output لتكون النتيجة في الخرج: 6 وفي حال كانت النتيجة الابتدائية هي صفر 0، يمكننا اختصار تمريرها مثل وسيط للتابع وتمرير الكتلة البرمجية له فقط، إذ ستكون القيمة الابتدائية هي قيمة أول عنصر من المصفوفة: output = [1,2,3].reduce {|result, current| result += current } print output ويكون الخرج: 6 يمكن تحديد تابع ثنائي binary أو تابع عادي على كائن واحد يقبل كائنًا آخر مثل وسيط له ليٌستدعى على كل عنصر من عناصر المصفوفة، ويستخدم التابع reduce نتيجة ذلك الاستدعاء لإنشاء قيمة واحدة تدل على النتيجة النهائية، وعند جمع عددين مثلًا 2 + 2 في روبي، يٌفهم ذلك على أنه استدعاء لتابع الجمع + على كائن العدد 2 كما يلي: 2.+(2) # 4 يمكن استخدام الصيغة 2 + 2 في روبي لتسهيل القراءة لتلك العبارة البرمجية، وينتج عنها داخليًا في اللغة استدعاء لتابع الجمع + على العدد 2، وبما أن التابع reduce يسمح بتحديد تابع ثنائي لاستدعائه على كل عنصر من عناصر المصفوفة مع النتيجة، فيمكن تمرير اسمه على شكل رمز، مثل تمرير الرمز +: للتابع reduce مثلًا لاستدعاء تابع الجمع على كل عنصر لجمع عناصر المصفوفة: output = [1, 2, 3].reduce(:+) print output لتكون النتيجة في الخرج: 6 يمكن استخدام التابع reduce لتنفيذ العديد من العمليات على عناصر المصفوفة، إذ يمكن استخدامه لتحويل تلك القيم، كما يمكن استخدامه لتقليص قيم المصفوفة للحصول على قيمة مفردة، ولكن لا توجد قاعدة تنص على أن القيمة المفردة ليست بمصفوفة جديدة. إذا كان لدينا قائمة من القيم التي نريد تحويلها إلى أعداد صحيحة، ولكن نريد فقط اختيار القيم التي يمكن تحويلها إلى أعداد صحيحة، فيمكن استخدام التابع reject لإزالة القيم غير العددية، ثم استخدام التابع map لتحويل القيم المتبقية إلى أعداد صحيحة، أو يمكن فعل كل ذلك في خطوة واحدة باستخدام التابع reduce باستخدام مصفوفة فارغة مثل قيمة ابتدائية ثم نحول القيمة الحالية إلى عدد صحيح باستخدام التابع Integer في الكتلة البرمجية المُمررة، وإذا كانت القيمة غير قابلة للتحويل إلى عدد صحيح سيرمي التابع Integer استثناء يمكننا التقاطه وتبديل القيمة الحالية بالقيمة nil، ثم إضافة القيمة الحالية إلى مصفوفة النتيجة فقط إذا لم تكن nil على النحو التالي: values = ["1", "2", "a", "3"] integers = values.reduce([]) do |array, current| val = Integer(current) rescue nil array.push(val) unless val.nil? array end print integers سيكون الخرج: [1,2,3] كما لاحظنا، يُستخدم التابع reduce في حال كانت النتيجة النهائية من المصفوفة قيمة مفردة أو مصفوفة جديدة بطول مختلف عن المصفوفة الأصلية. الخاتمة تعرفنا في هذا المقال على العديد من الطرق للتعامل مع المصفوفات، إذ تعلمنا كيفية الوصول إلى عناصرها والبحث واستخراج بعض القيم منها وترتيب العناصر وتحويل البيانات ضمنها وإنشاء مصفوفات وسلاسل نصية وقيمة مجموع كلي جديدة. تفيد كل تلك المفاهيم في حل العديد من المشاكل البرمجية باستخدام لغة روبي وهي من أهم المهارات التي يجب على المبرمج اكتسابها. لمزيد من التفصيل، ارجع إلى صفحة النوع Array في موسوعة حسوب. ترجمة -وبتصرف- للمقال How To Use Array Methods in Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: التعامل مع المصفوفات في لغة روبي مقال السلاسل النصية في لغة روبي دليلك الشامل إلى أنواع البيانات
-
المصفوفة array هي شكل من أشكال البيانات التي توفرها معظم لغات البرمجة والتي تسمح بتخزين عدة قيم تدعى عناصر معًا ضمن متغير واحد، ويمكن لهذه العناصر أن تكون من أنواع مختلفة من البيانات، مثل الأرقام والسلاسل النصية وغيرها من كائنات روبي. سنتعرف في هذا المقال على طرق إنشاء المصفوفات والوصول إلى القيم التي تحتويها، وإضافة وتعديل وحذف العناصر من المصفوفة، وطريقة المرور على عناصر المصفوفة، إذ تُعد المصفوفة في روبي كائنات مع توابعها الممكن استدعاؤها والاستفادة منها في التعامل مع عناصر المصفوفة. إنشاء مصفوفة في حال كان لدينا عدة قيم نحتاج التعامل معها ضمن البرنامج يمكننا تخزين كل منها في متغير خاص على النحو التالي: shark1 = "Hammerhead" shark2 = "Great White" shark3 = "Tiger" ولكن سنحتاج بتلك الطريقة لاختيار اسم متغير لكل قيمة منها، وفي حال كانت القيم كثيرة سيصبح الأمر صعبًا، وإذا أردنا إضافة قيمة جديدة إليها يجب تعريف متغير جديد وتذكُّر اسمه لاحقًا. هنا تأتي المصفوفات لحل تلك المشكلة عبر تخزين كل تلك القيم ضمن متغير واحد فقط. لإنشاء مصفوفة في روبي يمكن كتابة قيم العناصر ضمن أقواس معقوفة [] والفصل بينها بفواصل , على النحو التالي: sharks = ["Hammerhead", "Great White", "Tiger"] بدلًا من إنشاء ثلاثة متغيرات منفصلة أصبح لدينا الآن متغير واحد يحتوي على جميع القيم، ولإضافة قيمة جديدة للمصفوفة يمكن إضافتها مباشرةً دون الحاجة لإنشاء متغير جديد. يمكن طباعة كامل المصفوفة باستخدام التابع print، الذي سيعرض محتويات المصفوفة كما يلي: print sharks ليكون الخرج على النحو التالي: ["Hammerhead", "Great White", "Tiger"] توفر لغة روبي طريقةً مختصرةً لإنشاء مصفوفة يكون فيها كل مُدخل هو كلمة واحدة، وذلك بكتابة الكلمات بين القوسين في الصيغة التالية {}w% على النحو التالي: days = %w{Monday Tuesday Wednesday Thursday Friday Saturday Sunday} وسينتج عنها مصفوفة من الكلمات كما لو أننا عرفناها يدويًا ضمن أقواس معقوفة على النحو التالي: days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] ونلاحظ عدم الحاجة لاستخدام علامات الاقتباس لتعريف تلك الكلمات ضمن الصيغة {}w%، ولسنا مضطرين لتخزين عناصر من نفس النوع ضمن المصفوفة، ففي روبي يمكن للمصفوفات أن تحتوي على أي نوع من القيم بما في ذلك مصفوفات أخرى، وفيما يلي مثال على مصفوفة تحتوي على سلسلة نصية والقيمة nil وعدد صحيح ومصفوفة من السلاسل النصية: record = [ "Sammy", null, 7, [ "another", "array", ] ] سنتعرف في الفقرة التالية على كيفية الوصول إلى البيانات المخزنة ضمن المصفوفات. الوصول لعناصر المصفوفة للوصول إلى عنصر محدد ضمن المصفوفة يمكننا الإشارة إلى رقم الفهرس لذلك العنصر، أي ترتيب موقعه ضمن المصفوفة، إذ تبدأ الفهارس في روبي ومعظم لغات البرمجة الأخرى من الرقم صفر، فمثلًا للوصول إلى العنصر الأول من المصفوفة يمكننا تمرير رقم الفهرس له بعد اسم المتغير وبين الأقواس المعقوفة: sharks = ["Hammerhead", "Great White", "Tiger"] تحتوي المصفوفة sharks على ثلاثة عناصر، ورقم الفهرس لكل عنصر منها على النحو التالي: 2 1 0 Tiger Great White Hammerhead العنصر الأول في المصفوفة وهو Hammerhead له الفهرس رقم 0، أما العنصر الأخير في المصفوفة وهو Tiger له الفهرس رقم 2، إذ وعلى عكس طريقة العد التقليدية التي تبدأ بالرقم 1، نلاحظ أن الفهارس تبدأ من الصفر، لذا يجب الاعتياد على ذلك عند التعامل مع المصفوفات لتجنب الأخطاء. يمكن معرفة عدد العناصر الموجودة في المصفوفة باستخدام التابع length على النحو التالي: sharks.length ليكون الخرج على النحو التالي: 3 على الرغم من أن فهارس المصفوفة sharks تبدأ من 0 وتنتهي بالفهرس 2، إلا أن تابع طول المصفوفة length يعيد عدد العناصر الموجودة في المصفوفة والذي يساوي 3، ولا علاقة له برقم الفهرس للعناصر. ملاحظة: يمكنك التفكير في الفهرس على أنه إزاحة offset، أي عدد الأماكن من بداية المصفوفة، إذ يكون العنصر الأول في المقدمة وبالتالي بدون إزاحة، أي سيكون الفهرس 0، أما العنصر الثاني فهو على بُعد عنصر واحد من بداية المصفوفة، لذلك ستكون إزاحته بمقدار 1 وقيمة الفهرس 1. لمعرفة رقم الفهرس لعنصر محدد ضمن المصفوفة مثل العنصرTiger يمكن استخدام التابع ()index على النحو التالي: print sharks.index("Tiger") ليعطي الخرج التالي: 2 يعيد هذا التابع رقم الفهرس لأول عنصر من المصفوفة يحتوي على النص المحدد، وإذا لم يُعثر على عنصر مطابق لتلك القيمة فسيعيد التابع القيمة nil: print sharks.index("Whale") سيكون هنا الخرج: nil للحصول على آخر عنصر من المصفوفة يمكن استخدام رقم الفهرس السالب 1-: print sharks[-1] وسيكون الخرج كما يلي: "Tiger" ويوجد أيضًا التابعين first و last في روبي للحصول على أول وآخر عنصر من المصفوفة دون الحاجة إلى استخدام رقم الفهرس لها: puts sharks.first puts sharks.last ويكون الخرج: "Hammerhead" "Tiger" وعند محاولة الوصول إلى فهرس غير موجود: sharks[10] سينتج عن ذلك القيمة nil: nil يمكن أن تحتوي المصفوفات على مصفوفات أخرى ضمنها لينتج من ذلك مصفوفات متداخلة nested arrays يمكن الاستفادة من هذا الشكل من البيانات لتمثيل البيانات ثنائية الأبعاد في البرنامج، ويمكنك الاطلاع على مزيدٍ من المعلومات عن المصفوفات وأنواعها من خلال المقال المصفوفات Arrays في روبي، وفيما يلي مثال على مصفوفة متداخلة: nested_array = [ [ "salmon", "halibut", ], [ "coral", "reef", ] ] للوصول إلى العناصر الموجودة ضمن مصفوفة متداخلة يجب إضافة رقم فهرس إضافي يتوافق مع المصفوفة الداخلية، فمثلًا يمكن الوصول إلى القيمة coral من المصفوفة السابقة كما يلي: print nested_array[1][0]; ليكون الخرج على النحو التالي: coral نستخرج في هذا المثال العنصر الثاني أولًا من المصفوفة عبر رقم الفهرس 1 من المتغير nested_array، وهو عنصر المصفوفة ["coral", "reef"]، ثم نستخرج العنصر الأول من هذه المصفوفة عبر رقم الفهرس 0 لنحصل بذلك على القيمة المطلوبة "coral". سنتعرف في الفقرة التالية على طريقة إضافة عناصر إلى المصفوفة. إضافة عناصر إلى المصفوفات لنعود إلى المصفوفة السابقة sharks، التي تحتوي على ثلاثة عناصر تبدأ من رقم الفهرس 0 إلى 2: sharks = ["Hammerhead", "Great White", "Tiger"] لإضافة عنصر جديد إلى تلك المصفوفة، يمكن تعيين قيمة للفهرس الذي يلي آخر عنصر منها والذي في هذه الحالة هو 3 كما يلي: sharks[3] = "Whale"; print sharks ليكون الخرج على النحو التالي: ["Hammerhead", "Great White", "Tiger", "Whale"] قد يؤدي استخدام هذه الطريقة إلى حدوث أخطاء، فإذا أضفنا عنصرًا إلى رقم فهرس ما أكبر من ذلك فسيؤدي ذلك لإضافة العنصر nil داخل المصفوفة على النحو التالي: sharks[5] = "Sand"; print sharks; ويكون الخرج في هذه الحالة: ["Hammerhead", "Great White", "Tiger", "Whale", nil, "Sand"] وعند محاولة الوصول إلى قيمة ذلك العنصر الزائد سنحصل على القيمة nil: sharks[4] سيكون الخرج: nil يمكن استخدام التابع push لتجنب هذه الأخطاء، إذ يضيف هذا التابع عنصرًا إلى نهاية المصفوفة دون مشاكل: sharks.push("Thresher") print sharks وعندها سيكون الخرج: ["Hammerhead", "Great White", "Tiger", "Whale", nil, "Whale", "Thresher"] يمكن أيضًا استخدام الصيغة >> بدلًا من التابع push لإضافة عنصر إلى نهاية المصفوفة كما يلي: sharks << "Bullhead" ويكون الخرج: ["Hammerhead", "Great White", "Tiger", "Whale", nil, "Whale", "Thresher", "Bullhead"] نستخدم التابع ()unshift لإضافة عنصر إلى بداية المصفوفة كما يلي: sharks.unshift("Angel") print sharks ليعطي الخرج التالي: ["Angel", "Hammerhead", "Great White", "Tiger", "Whale", nil, "Whale", "Thresher", "Bullhead"] سنتعرف في الفقرة التالية على طريقة إزالة عناصر من المصفوفة. إزالة العناصر من المصفوفات يمكن استخدام التابع delete أو delete_at لإزالة عنصر محدد من المصفوفة. لنختبر ذلك بإزالة العنصر الفارغ داخلها والذي أنشأناه عن طريق الخطأ، ونبدأ بالعثور على موضعه في المصفوفة باستخدام التابع index على النحو التالي: print sharks.index(nil) والذي يعطينا الخرج التالي: 4 ثم نستخدم التابع delete_at لإزالة ذلك صاحب الفهرس رقم 4 ونطبع المصفوفة لنتأكد: sharks.delete_at(4) print sharks سيكون الخرج على النحو التالي: ["Angel", "Hammerhead", "Great White", "Tiger", "Whale", "Thresher", "Bullhead"] يزيل التابع delete العناصر التي تتطابق مع القيمة التي تُمرّر له. لنختبر استخدامه بإزالة العنصر Whale من المصفوفة السابقة كما يلي: sharks.delete("Whale") print sharks; ويكون الخرج في هذه الحالة: ["Angel", "Hammerhead", "Great White", "Tiger", "Thresher", "Bullhead"] يزيل التابع delete جميع القيم المطابقة للقيمة التي نمررها له، لذا إذا كانت المصفوفة تحتوي على عناصر مكررة فستُزال جميعها، وبالمقابل يتيح التابع pop إزالة آخر عنصر من المصفوفة: sharks.pop print sharks; ليكون الخرج كما يلي: ["Angel", "Hammerhead", "Great White", "Tiger", "Thresher"] نلاحظ إزالة العنصر Bullhead، وهو آخر عنصر في المصفوفة. لإزالة أول عنصر من المصفوفة يمكن استخدام التابع shift على النحو التالي: sharks.shift print sharks ليكون الخرج: ["Hammerhead", "Great White", "Tiger", "Thresher"] نلاحظ إزالة العنصر Angel من بداية المصفوفة، ويمكن استخدام التابعين pop و shift لإزالة العناصر من بداية ونهاية المصفوفة كما لاحظنا، ويفضل استخدام التابع pop عند الحاجة لأن حذف العنصر من نهاية المصفوفة لن يؤثر على رقم الفهرس لباقي العناصر. تعدل كلًا من التوابع delete_at و pop و shift على قيمة المصفوفة الأصلية وتعيد العنصر المحذوف: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] deleted_at_element = sharks.delete_at(1) popped_element = sharks.pop puts "Deleted_at element: #{deleted_at_element}" puts "Popped element: #{popped_element}" puts "Remaining array: #{sharks}" وسيكون الخرج: Deleted_at element: Great White Popped element: Whale Remaining array: ["Hammerhead", "Tiger"] بعد أن تعرفنا على طرق إزالة العناصر من المصفوفة، سنتعرف في الفقرة التالية على طريقة التعديل على قيمة عنصر من المصفوفة. تعديل عناصر موجودة في المصفوفات يمكن تحديث قيمة عنصر من المصفوفة عن طريق تعيين قيمة جديدة لفهرس ذلك العنصر باستخدام معامل الإسناد تمامًا كما نُعرف المتغيرات العادية. تحتوي المصفوفة التالية على العنصر "Hammerhead" في الفهرس رقم 0، لنحاول تعديل قيمة ذلك العنصر إلى القيمة "Angel": sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] sharks[0] = "Angel" print sharks; سيكون الخرج على النحو التالي: ["Angel", "Great White", "Tiger", "Whale"] يمكن التأكد من تعديل قيمة ذلك العنصر بالبحث عن القيمة الجديدة باستخدام التابع index لنلاحظ أن القيمة الجديدة موجودة في نفس المكان. سنتعرف في الفقرة التالية على طريقة المرور على عناصر المصفوفة. المرور على عناصر المصفوفة توفر لغة روبي العديد من الطرق للمرور على عناصر المصفوفة، ويختلف التابع المُستخدم بحسب الهدف الذي نريده. لنبدأ بالمرور على عناصر المصفوفة وطباعة قيمة كل منها وذلك باستخدام الصيغةfor..in على النحو التالي: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] for shark in sharks do puts shark end تعيّن روبي قيمة كل عنصر في المتغير المحلي shark، ويمكنك بعد ذلك طباعة قيمة ذلك العنصر باستخدام التابع puts. على الرغم من إمكانية استخدام الحلقة التكرارية for..in للمرور على عناصر المصفوفة، إلا أن المصفوفات في لغة روبي هي كائنات ويتوفر ضمنها التابع each، الذي يعمل بنفس طريقة الحلقة التكرارية for..in ولكن بصيغة مختلفة: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] sharks.each do |shark| puts shark end يستخدم التابع each صيغةً تتكرر كثيرًا في لغة روبي، إذ يأخذ التابع كتلة block مثل وسيط، ويُقصد بالكتلة هنا شيفرة برمجية ستنفذ لاحقًا في سياق التابع. ستُنفذ في مثالنا السابق العبارة البرمجية puts shark، أما الكلمة shark المحاطة بعلامات الأنبوب | هي المتغير المحلي الذي يمثل العنصر الحالي في المصفوفة، إذ تسنِد لغة روبي قيمة العنصر الحالي إلى هذا المتغير وتنفذ بعدها التعبير الموجود ضمن الكتلة، ويكرّر التابع each هذه العملية لكل عنصر من عناصر المصفوفة، ويكون الخرج على النحو التالي: Hammerhead Great White Tiger Whale يمكن استخدام الصيغة المختصرة ضمن الأقواس المعقوصة curly braces {} بدلًا من كلمتي do و end إذا كانت الكتلة مكونةً من سطر برمجي واحد فقط: ... sharks.each {|shark| puts shark } وسينتج عنها نفس الخرج السابق. يعمل التابع each_with_index بنفس طريقة التابع each، ولكنه يتيح الوصول إلى رقم الفهرس للعنصر الحالي في المصفوفة. لنختبر ذلك بطباعة رقم الفهرس والقيمة لكل عنصر من المصفوفة: sharks = ["Hammerhead", "Great White", "Tiger", "Whale"] sharks.each_with_index do |shark, index| puts "The index is #{index}" puts "The value is #{shark}" end تسند روبي قيمة كل عنصر في المصفوفة إلى المتغير shark، وتسند رقم الفهرس الحالي إلى المتغير index في كل مرة، ويمكن بعد ذلك الإشارة إلى هذين المتغيرين داخل الكتلة بحيث يمكن الوصول إلى قيمة العنصر الحالي ورقم فهرسه في المصفوفة. The index is 0 The value is Hammerhead The index is 1 The value is Great White The index is 2 The value is Tiger The index is 3 The value is Whale تعلمنا في هذه الفقرة عدة طرق للمرور على عناصر المصفوفة، وهي من أكثر المهمات التي نحتاجها عند التعامل مع المصفوفات. الخاتمة المصفوفات في روبي ومعظم لغات البرمجة من المفاهيم المهمة والقوية والتي يجب التعرف عليها وطرق التعامل معها. تعرّفنا في هذا المقال على المصفوفات وكل المهمات الأساسية للتعامل معها من إنشاء المصفوفات والوصول إلى عناصرها وإضافة وإزالة وتعديل العناصر ضمن المصفوفة، وتعرفنا على طريقتين للمرور على عناصر المصفوفة وعرض محتوياتها. لمزيد من التفصيل، ارجع إلى صفحة النوع Array في موسوعة حسوب. ترجمة -وبتصرف- للمقال How To Work with Arrays in Ruby لصاحبيه Brian Hogan و Tony Tran. اقرأ أيضًا المقال السابق: توابع السلاسل النصية في لغة روبي Ruby المصفوفات (Arrays) في روبي أنواع البيانات Data Types في لغة روبي
-
تحتوي السلاسل النصية في لغة روبي على العديد من التوابع ضمنها والتي تسهل العديد من العمليات الشائعة على السلاسل النصية سنتعرف عليها في هذا المقال، والتي تفيد مثلًا في تحديد طول السلسلة النصية أو تقسيمها واستخراج أجزاء منها، أو إضافة وإزالة المسافات البيضاء whitespace والأحرف الأخرى منها، أو تغيير حالة الأحرف فيها، أو البحث عن نص معين فيها واستبداله بآخر، لينتج عن استخدام تلك التوابع مزايا قوية يمكن الاستفادة منها لحل العديد من المشاكل. تحديد طول سلسلة نصية تحتوي السلاسل النصية على التابع length والذي يُعيد عدد الأحرف في السلسلة النصية، فمثلًا يمكن الاستفادة من ذلك التابع لتحديد الحد الأدنى أو الأقصى لطول كلمة المرور في نظام ما والعديد من الاستخدامات، والمثال التالي يوضح طول سلسلة نصية مُخزنة ضمن متغير ويطبع النتيجة: open_source = "Sammy contributes to open source." print open_source.length ليكون الخرج على النحو التالي: 33 يجب التأكيد على أن كل المحارف ضمن السلسلة النصية بما في ذلك الحروف والأرقام والمسافات والرموز مأخوذة بالحسبان ضمن طول السلسلة النصية، ولمعرفة ما إذا كانت السلسلة النصية فارغة يمكن مقارنة طولها والتأكد بأنه يساوي الصفر 0، أو يمكن استخدام التابع المخصص لذلك وهو ?empty على النحو التالي: name = "" name.empty? # true name = "Sammy" name.empty? # false name = " " name.empty? # false سنتعرف في الفقرة التالية على طريقة الوصول إلى المحارف ضمن السلسلة النصية. الوصول إلى المحارف ضمن السلسلة النصية توفر لغة روبي التابع slice، الذي يفيد في استخراج جزء من السلسلة النصية. يُشار لكل محرف ضمن السلسلة النصية برقم الفهرس لها وهو رقم يبدأ ترتيبه من الصفر 0، تمامًا كما هو الحال بالنسبة للمصفوفات arrays في روبي، فمثلًا رقم الفهرس للمحارف في السلسلة النصية Sammy هو: 4 3 2 1 0 y m m a S يسمح التابع slice بالوصول إلى محرف واحد أو مجال من المحارف، فعند تمرير رقم صحيح واحد غليه سيُعيد المحرف الموجود في رقم الفهرس المحدد، وعند تمرير رقمين صحيحين مفصولين بفاصلة سيُعيد هذا التابع جميع المحارف بدءًا من رقم الفهرس الأول وحتى رقم الفهرس الثاني، ويمكن تمرير نطاق من الفهارس مثلًا من واحد إلى أربعة 4..1 لاستخراج الحروف الموجودة ضمن هذا النطاق: "Sammy".slice(0) # "s" "Sammy".slice(1,2) # "am" "Sammy".slice(1..4) # "ammy" ويمكن استخدام الصيغة [] والتي هي مرادف للتابع slice ليصبح شكل التعامل مع السلاسل النصية مشابه للمصفوفات: "Sammy"[0] # "s" "Sammy"[1,2] # "am" "Sammy"[1..4] # "ammy" يمكن الوصول إلى المحارف في نهاية السلسلة النصية باستخدام رقم فهرس سالب، فمثلًا الفهرس 1- يعيد المحرف الأخير من السلسلة النصية، والفهرس 2- يعيد المحرف ما قبل الأخير وهكذا، ويمكن تحويل سلسلة نصية إلى مصفوفة تحتوي على المحارف المكونة لها باستخدام التابع chars على النحو التالي: "sammy".chars # ["S", "a", "m", "m", "y"] يمكن استخدام هذه الطريقة للتعديل على محارف السلسلة النصية، وسنتعرف في الفقرة التالية على طريقة تغيير حالة الأحرف ضمن السلسلة النصية. تعديل حالة الأحرف يمكن استخدام التابع upcase لتحويل جميع الحروف الأجنبية في السلسلة النصية إلى أحرف كبيرة، والتابع downcase لتحويل جميع الحروف الأجنبية في السلسلة النصية إلى أحرف صغيرة، وتبقى الحروف بغير اللغة الإنجليزية كما هي دون تغيير. سنعدّل في المثال التالي السلسلة النصية "Sammy Shark" إلى حروف كبيرة: name = "Sammy Shark" print name.upcase SAMMY SHARK ليكون الخرج على النحو التالي: SAMMY SHARK ولنختبر تحويلها إلى أحرف صغيرة: print name.downcase سيكون الخرج هنا: sammy shark يسهّل استخدام التابعين upcase و downcase لمقارنة السلاسل النصية مع بعضها عن طريق توحيد حالة الأحرف قبل المقارنة، فمثلًا عند إدخال اسم مستخدم في برنامج ما من قِبل المستخدم بأحرف كبيرة، فيمكن تحويل القيمة المُدخلة إلى حروف صغيرة ثم مقارنتها القيم الموجودة ضمن النظام بأحرف صغيرة. توفر روبي أيضًا التابع capitalize، الذي يعيد نسخةً جديدةً من السلسلة النصية مع تحويل الحرف الأول منها إلى حرف كبير: "sammy".capitalize # "Sammy" وتوفر روبي أيضًا التابع swapcase والذي يعيد نسخةً جديدةً من السلسلة النصية مع تبديل حالة كل الأحرف فيها: text = "Sammy" print text.swapcase وسيكون الخرح في هذه الحالة: sAMMY تعيد التوابع downcase و upcase و capitalize و swapcase نسخةً جديدةً من السلسلة النصية وتترك السلسلة الأصلية دون تغيير، ويتوضح ذلك عند التعديل على قيمة سلسلة نصية مُخزنة ضمن متغير ما، فتبقى القيمة ضمن المتغير كما هي دومًا دون تغيير في حال لم نسنِد القيمة الجديدة مجددًا إلى المتغير: text = "sammy" text.capitalize print "Hello, #{text}!" ويكون الخرج على النحو التالي: Hello, sammy! على الرغم من استخدامنا للتابع capitalize على المتغير text في المثال السابق، وبما أننا لم نخزن القيمة المُعادة من capitalize مجددًا داخل المتغير text، فقيمة المتغير text لم تتغير، ولتغييرها يمكن إعادة كتابة الشيفرة السابقة بالشكل التالي لحل هذه المشكلة: text = "sammy" text = text.capitalize print "Hello, #{text}!" ويكون الخرح: Hello, Sammy! يمكن استخدام التوابع !downcase و !upcase و !capitalize و !swapcase (لاحظ انتهاء أسمائها بالمحرف !) للتعديل على قيمة السلسلة النصية الأصلية بدلًا من إنشاء سلسلة جديدة: text = "sammy" text = text.capitalize! print "Hello, #{text}!" ويجب الانتباه عند التعديل على قيمة السلسلة النصية الأصلية؛ فقد تعدّل قيمة سلسلة نصية في مكان ما ضمن البرنامج دون الانتباه إليه ما يؤدي لمشاكل، وسنتعرف في الفقرة التالية على طريقة إضافة وإزالة المسافات البيضاء من السلاسل النصية. إضافة وإزالة المسافات البيضاء الفارغة يمكن إضافة المسافات قبل أو بعد أو على كلا طرفي السلسلة النصية لتعديل تنسيقها قبل طباعتها مثلًا، وقد نحتاج أحيانًا لإزالة الأحرف غير الضرورية من بداية أو نهاية السلاسل النصية مثل المسافات الزائدة أو المحارف الخاصة. نبدأ بالتعرف على التابع center، الذي نمرر له طول السلسلة النصية الناتجة التي نريدها وسيضمن أنها بالطول المحدد، وذلك بإضافة مسافات بيضاء حول السلسلة نصية التي يُعالجها على النحو التالي: "Sammy",center(21) # " Sammy " ويمكننا استبدال المسافات البيضاء بنص معين نريد إضافته بتمريره لذلك التابع مثل وسيط ثانِ على النحو التالي: " [Sammy] ".center(21, "<>") # "<><><> [Sammy] <><><>" يضيف التابعان ljust و rjust مسافات بيضاء أو أحرف معينة إلى الجهة اليسرى أو اليمنى من السلسلة النصية ويعملان بالطريقة نفسها التي يعمل بها التابع center: "Sammy".ljust(20) # "Sammy " "Sammy".rjust(20) # " Sammy" "Sammy".rjust(20, "!") # "!!!!!!!!!!!!!!!Sammy" يمكن استخدام التابع rstrip لإزالة المسافات الزائدة في بداية السلسلة النصية، كما يمكن استخدام التابع lstrip لإزالة المسافات الزائدة في نهاية السلسلة النصية، أما التابع strip فيزيل المسافات الزائدة في كلا الجهتين (البداية والنهاية) من السلسلة النصية: " Sammy".rstrip # "Sammy" "Sammy ".lstrip # "Sammy" " Sammy ".strip # "Sammy" يمكن أيضًا استخدام التوابع !center و !ljust و !rjust و !lstrip و !rstrip و !strip لتعديل قيمة السلسلة النصية الأصلية، وفي بعض الأحيان قد نحتاج لإزالة الأحرف من نهاية السلسلة النصية لذلك يمكن استخدام التابع chop لإزالة المحرف الأخير من السلسلة النصية: "Sammy".chop # "Samm" قد نستفيد من ذلك لإزالة محرف السطر الجديد n\ مثلًا من نهاية سلسلة نصية ما: "This string has a newline\n".chop يترك التابع chop السلسلة النصية الأصلية دون تعديل ويعيد قيمة سلسلة نصية جديدة بدون المحرف الأخير، بينما يعدل التابع !chop السلسلة النصية الأصلية بدلًا من إنشاء سلسلة جديدة، ويمكن استخدام التابع chomp لإزالة عدة محارف من نهاية السلسلة النصية على النحو التالي: "Sammy".chomp("my") # "Sam" وفي حال لم نحدد السلسلة النصية التي نريد إزالتها من النهاية فسيزيل التابع chomp محرف السطر الجديد n\ فقط: "This string has a newline\n".chomp # "This string has a newline وفي حال لم تحتوي السلسلة النصية على محرف السطر الجديد n\ فسيعيد التابع chomp السلسلة النصية الأصلية كما هي دون تعديل: "Sammy".chomp # "Sammy" استخدام التابع chomp أسهل لإزالة محارف السطر الجديد n\ من نهاية السلسلة النصية من التابع chop والذي يزيل دائمًا الحرف الأخير مهما كان نوعه، كما توفر لغة روبي التابع !chomp، الذي يعدل قيمة السلسلة النصية الأصلية ويعيد السلسلة النصية المعدلة إذا أزال محارف من نهاية السلسلة، ولكن وعلى عكس التابع chomp فسيعيد التابع !chomp القيمة nil إذا لم يُعدل على السلسلة النصية: string = "Hello\n" string.chomp! # "Hello" string = "Hello" string.chomp! # nil سنتعرف في الفقرة التالية على طريقة البحث ضمن السلاسل النصية. البحث عن المحارف والنصوص نحتاج أحيانًا لتحديد ما إذا كانت السلسلة النصية تحتوي على نص معين أم لا، لهذا الغرض توفر روبي التابع ?include، الذي يتحقق مما إذا كانت السلسلة النصية تحتوي على سلسلة نصية أخرى، ويعيد القيمة المنطقية true إذا كانت السلسلة النصية تحتوي على السلسلة النصية الأخرى، أو القيمة false في حال لم تحتويها: "Sammy".include?("a") # true "Sammy".include?("b") # false يعيد التابع index رقم الفهرس لمكان محرف أو سلسلة نصية ما، أو يعيد القيمة nil في حال عدم العثور على المحرف أو السلسلة النصية الجزئية ضمنها: "Sammy".index("a") # 1 "Sammy".index("mm") # 2 "Sammy".index("Fish") # nil يبحث التابع index عن أول تواجد للمحرف أو السلسلة النصية المطلوبة فقط ويعيد رقم الفهرس له. لنوضح ذلك في المثال التالي على سلسلة نصية طويلة: text = "Sammy has a balloon" text.index("a") # 1 تحتوي السلسلة النصية "Sammy has a balloon" على أربعة حروف "a"، ولكن التابع index يعيد فقط موقع أول ظهور لذلك الحرف، ويجب أخذ ذلك بالحسبان عند البحث ضمن السلاسل النصية، ولتحديد كل ظهور للحرف "a" ضمن الجملة السابقة يمكن تحويل السلسلة النصية إلى مصفوفة من الأحرف المكونة لها ثم الاستعانة بتوابع المصفوفة للبحث بين تلك الحروف عن كل مكان لذلك الحرف وتحديد رقم الفهرس له مثلًا على النحو التالي: text = "Sammy has a balloon" indices = text.chars .each_with_index .select{|char, index| char == "a" } .map{|pair| pair.last} print indices [1, 7, 10, 13] ليكون الخرج على النحو التالي: [1, 7, 10, 13] يعيد التابع each_with_index مصفوفة ثنائية الأبعاد يحتوي كل عنصر منها على الحرف ومكانه ضمن السلسلة النصية، ونختار ضمن التابع select العناصر التي تحتوي على الحرفa، ثم نحول تلك العناصر باستخدام التابع map إلى مصفوفة تحتوي على رقم الفهرس لكل تواجد للحرف a، ويمكن التحقق مما إذا كانت السلسلة النصية تبدأ بحرف أو سلسلة نصية معينة باستخدام التابع ?start_with كما يلي: text = "Sammy has a balloon" text.start_with?("s") # true text.start_with?("Sammy has" # true يقبل التابع ?start_with سلسلةً نصيةً واحدةً أو أكثر مثل وسطاء له، ويعيد true إذا تطابق أي من تلك السلاسل النصية مع بداية السلسلة الأصلية: text = "Sammy has a balloon" text.start_with?("Sammy the Shark", "Sammy") # true لم يُعثر في هذا المثال على السلسلة النصية "Sammy the Shark"، ولكن عُثر على السلسلة النصية "Sammy"، لذا أرجع التابع القيمة true، وبالمقابل يمكن استخدام التابع ?end_with للتحقق مما إذا كانت السلسلة النصية تنتهي بسلسلة نصية معينة، إذ يعمل هذا التابع بنفس طريقة عمل التابع ?start_with: text = "Sammy has a balloon" text.end_with?("balloon") # true text.end_with?("boomerang") # false text.end_with?("boomerang", "balloon") # true بعد أن تعرفنا على طرق البحث ضمن النصوص، سنتعرف في الفقرة التالية على طريقة استبدال أجزاء معينة من السلسلة النصية. استبدال أجزاء من النص في السلاسل النصية يمكن البحث عن سلسلة نصية واستبدالها بأخرى باستخدام خاصية البحث والاستبدال المتوفرة عادةً في محررات النصوص التي نستخدمها، ويمكن تنفيذ هذه الميزة في لغة روبي باستخدام التوابع sub و gsub، إذ يبدل التابع sub جزءًا من النص بجزء آخر. سنغيّر في المثال التالي الجزء "has" إلى "had" في الجملة التالية: balloon = "Sammy has a balloon" print balloon.sub("has","had") ليعيد التابع قيمة النص بعد التبديل: Sammy had a balloon. يستبدل التابع sub أول ورود للنص المطابق بالنص الجديد فقط. لتوضيح ذلك سنستخدم الجملة التالية المعدّلة التي تحتوي على ورودين للكلمة "has": balloon = "Sammy has a balloon. The balloon has a ribbon" print balloon.sub("has","had") وسيكون الخرج على النحو التالي: Sammy had a balloon. The balloon has a ribbon نلاحظ تبديل أول ظهور فقط، ولتبديل جميع الأماكن التي ترد فيها السلسلة النصية يمكن استخدام التابع gsub الذي يبدل على مستوى النص بأكمله: balloon = "Sammy has a balloon. The balloon has a ribbon" print balloon.gsub("has","had") Sammy had a balloon. The balloon had a ribbon ويكون الخرج: Sammy had a balloon. The balloon had a ribbon تعيد التوابع sub و gsub سلسلةً نصيةً جديدةً دون تعديل السلسلة الأصلية، ويمكن توضيح ذلك بتغيير الكلمة "balloon" إلى "boomerang" في السلسلة النصية التالية: text = "Sammy has a balloon" text.gsub("ballooon", "boomerang") print text Sammy has a balloon التي تعطي الخرج التالي: Sammy has a balloon نلاحظ بقاء النص كما هو على الرغم من إمكانية الاستبدال، وذلك لأننا لم نخزن نتيجة التابع gsub في المتغير مجددًا، وللحصول على النتيجة التي نريدها يمكننا تعديل الشيفرة كما يلي: text = "Sammy has a balloon" text = text.sub("ballooon", "boomerang") print text أو يمكن استخدام التابع !sub بدلًا من التابع subوالذي يعدل على السلسلة الأصلية بدلًا من إعادة سلسلة جديدة. سنستخدم في المثال التالي التابع !sub لتغيير الجملة "red balloon" إلى "blue boomerang": text = "Sammy has a red balloon" text.sub!("red", "blue") text.sub!("balloon", "boomerang") print text ليكون الخرج على النحو التالي: Sammy has a balloon يمكن استخدام التابع !gsub لاستبدال كل ورود للنص المحدد والتعديل على قيمة السلسلة النصية الأصلية، ويمكن تمرير التعابير النمطية Regular Expressions -أو اختصارًا regex- للتوابع sub و gsub للبحث عن نمط معين، ففي المثال التالي سنبدل جميع الحروف الصوتية ضمن النص بالرمز @: "Sammy has a red balloon".gsub(/[aeiou]/, "@") وسيكون الخرج على النحو التالي: "S@mmy h@s @ r@d b@ll@@n" ويمكن تمرير جدول Hash بدلًا من سلسلة نصية ثابتة لتحديد كيفية استبدال كل جزء من النص الوارد بصورةٍ مختلفة، ففي المثال التالي سنبدّل جميع حروف "a" إلى المحرف "@" وجميع الحروف "o" إلى الرقم صفر: "Sammy has a red balloon".gsub(/[aeiou]/, {"a" => "@", "o" => "0"}) # "S@mmy h@s @ rd b@ll00n" نلاحظ أن ذلك يمنحنا إمكانية إجراء عمليات تبديل معقدة على النصوص والذي يفيدنا في حل بعض المشاكل المعقدة. الخاتمة تعرفنا في هذا المقال على العديد من التوابع التي تسمح لنا بالتعامل مع السلاسل النصية باستخدام توابع مبينة مسبقًا لنوع البيانات الخاص بالسلاسل النصية، ومنها ما كان يترك السلسلة الأصلية دون تعديل ومنها من يعدّل عليها. يعتمد اختيار التوابع التي سنستخدمها على احتياجاتنا، إذ تمنح لغة روبي المبرمجين المرونة لاختيار التوابع التي تفيدهم في العمل مع البيانات الخاصة بهم، وينصح باستخدام التوابع التي لا تعدل على القيمة الأصلية لأنها أسهل في تطوير البرامج وتجنب الأخطاء. لمزيد من التفصيل، ارجع إلى صفحة النوع String في موسوعة حسوب. ترجمة -وبتصرف- للمقال How To Work with String Methods in Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: السلاسل النصية في لغة روبي Ruby السلاسل والمتغيرات في روبي الدليل السريع إلى لغة البرمجة روبي
-
يتطور مع الوقت مفهومنا لتوزيع المسؤوليات بين أعضاء الفرق المسؤولة عن تطوير المنتجات الرقمية، فسواءً كنت مبرمجًا أو مدير أنظمة قد تستفيد من تطبيق ممارسات DevOps أي إدارة العمليات Development Operations) لتحسين المنتج النهائي ولتطوير طريقة عملك ورفع الإنتاجية. في هذا المدخل الشامل ستتعرف المبادئ الأساسية لممارسات DevOps وخارطة الطريق لتعلم DevOps هذا المجال الشيق واحترافه. ما هو DevOps؟ كلمة DevOps هي اختصار لكلمتي التطوير Development، والعمليات Operations وتشير إلى الممارسات التي تهدف لدعم تطوير وتشغيل المنتجات الرقمية بمختلف أنواعها، سواءً لتسريع عملية التطوير أو لرفع جودة المنتج وصولًا لتسهيل عملية تشغيله ونشره وتوصيله للمستخدمين ثم مراقبة أدائه الفعلي وعمله، حيث كانت هذه الأعمال والمسؤوليات سابقًا موزعة على فريقين منفصلين -سنتعرف عليهما في الفقرة التالية- ما أثر على كفاءة عمل هذه الفرق وخلق مشاكل سنتعرف عليها في الفقرات القادمة. قد تتساءل، ما هي وظيفة DevOps ومن هما فريق التطوير وفريق العمليات؟ هدف DevOps الأساسي مساعدة من يتبنى تلك العمليات على رفع السرعة والكفاءة والإنتاجية في تطوير التطبيقات وبرمجتها، ففي السابق كانت تلك العمليات التي يتضمنها DevOps موزعة بين فريقين، وهما فريق التطوير المسؤول عن كتابة شيفرة البرنامج وتطويره والمحافظة على جودة الشيفرة المصدرية له، وينتهي دور الفريق عند ذلك، والفريق الآخر هو فريق العمليات المسؤول عن استلام النسخة الجديدة من البرنامج من فريق التطوير ونشرها وضبطها ضمن بيئة الإنتاج، سواء بربط وتوفير الوصول للخدمات الخارجية التي يعتمد عليها البرنامج، وحتى إعداد البرنامج نفسه في تلك البيئة وتشغيله، ووصولًا مراقبة عمل البرنامج خلال مرحلة الإنتاج والحفاظ على الأداء ومستوى الوصول العالي للبرنامج، ثم إعلام فريق التطوير بأي مشاكل تحدث أثناء التشغيل تتطلب التطوير على البرنامج لإطلاق نسخة جديدة منه. تركز مهام DevOps على زيادة التعاون بين هذين الفريقين بما يخدم كل منهما ويؤدي بالنهاية لرفع جودة وكفاءة البرنامج النهائي، أي إلغاء الفصل التام السابق بين مهام الفريقين ليتشارك كلاهما ببعض المسؤوليات والمعلومات التي تُحسّن تصميم التطبيق وإطلاقه وترفع كفاءته خدمةً للمنتج النهائي، ويمكنك التعرف على المراحل التي يمر بها المطور لتبنّي ممارسات DevOps من مقال رحلة المطور عبر DevOps. لماذا أتعلم DevOps؟ بعد أن ينتهي فريق التطوير من تحضير نسخة جديدة من المنتج الرقمي تنتهي مسؤوليته هنا، وتمرر النسخة الجديدة إلى فريق العمليات الذي يباشر بتحضير ما يلزم لنشرها للمستخدمين، لكن ماذا لو اكتشف فريق العمليات أثناء ذلك مشكلة في تلك النسخة؟ ففي هذه الحال يجب إخبار فريق التطوير بذلك كي يعدلوا على النسخة. عملية استلام النسخة ومحاولة نشرها واكتشاف الخطأ ثم إخبار فريق التطوير به تأخذ وقتًا وتحتاج لتواصل فعال لتوضيح المشكلة لكلا الفريقين، لذا يبدو أن هناك عملًا مشتركًا بين الفريقين، يبدأ منذ انتهاء تطوير نسخة جديدة من المنتج الرقمي وصولًا لنشرها ومراقبة عملها، هنا تأتي ممارسات DevOps المختلفة والتي لها علاقة بالتعاون بين المطور والمسؤول عن تشغيل المنتج، فتزيد من سرعة توصيل المنتج وترفع من جودته، كما تساعد كلا الفريقين في زيادة الإنتاجية عبر أتمتة الكثير من أعمال تسليم النسخة الجديدة من المنتج وفحصها واكتشاف الأخطاء مبكرًا وصولًا إلى نشرها، وذلك بإزالة الحاجز الذي كان موجود مسبقًا بين المطور ومسؤول العمليات. وللاطلاع أكثر على أهداف DevOps يمكنك قراءة مقال ما هي الغاية من DevOps؟. تعلم DevOps: الأساسيات سواء كنت ضمن فريق المطورين أو العمليات وتتبع ممارسات DevOps هناك بعض الأساسيات الواجب على كلا الفريقين الاطلاع عليها وتعلمها لزيادة سهولة التعاون والتواصل مع الفريق الآخر ومناقشة حلول المشكلات، يمكنك التعرف أكثر على استراتيجية DevOps من مقال ما المقصود بـ DevOps؟، والاطلاع على نموذج التفكير الذي ولّد تلك الممارسات على من مقال محاكاة عقلية DevOps. انضم لاحقًا مفهوم الأمان Security إلى ممارسات DevOps ليصبح مجموع تلك الممارسات مجتمعة تُعرف باسم DevSecOps (اختصارًا لعبارة Development Security Operations)، فبدلًا من النظر إلى نواحي الأمان للتطبيق كمرحلة أخيرة دُمج ذلك المفهوم منذ بداية عملية التطوير، كالاهتمام بطرق الاستيثاق من مستخدمي البرنامج أو تشفير البيانات المُخزنة وضمان تواصل البرنامج بقنوات مشُفرة مع الخدمات الأخرى لحماية البيانات المرسلة وغيرها من الممارسات التي يمكنك القراءة عنها وعن المفهوم بشكل عام من سلسلة مقالات تعرف على devopssec والبدء بالمقال ما هي DevSecOps؟. البنية التحتية البنية التحتية infrastructure هي مجموع المكونات الفيزيائية والافتراضية التي تدعم تطوير ونشر وعمل التطبيقات البرمجية، وتتألف المكونات الفيزيائية من الخوادم التي سنتعرف عليها في الفقرة التالية، وأجهزة التشبيك وأجهزة تخزين البيانات وكل الأجهزة الفيزيائية الداخلة في عمل التطبيقات البرمجية، والمكونات الافتراضية هي البرمجيات التي يتعامل معها التطبيق أو يعتمد عليها، لذا فالبنية التحتية هي جزء أساسي من ممارسات DevOps، فهي تخدم عملية التطوير والنشر والتوسيع للتطبيق. أساسيات الشبكات نحتاج لربط الحواسيب والأجهزة مع بعضها سواء لمشاركة البيانات أو للاستفادة من خدمات الأجهزة الأخرى، ويتم ذلك بربطها بشبكة معًا، لذا اطلاعك على أساسيات عمل شبكات الحاسوب لك كمطور عندما تطور برنامجًا يقدم خدمة أو يستفيد من خدمات أخرى عبر الشبكة، وأساسي إذا كنت ضمن فريق العمليات حيث تعتمد مهامك على إدارة الشبكة بين الخدمات وتوفير الوصول إليها وتطوير تصميم الشبكة لخدمة عمل البرنامج. يوجد عدة أنواع من الشبكات سواء من ناحية مكان تلك الأجهزة كالشبكات المحلية LAN أو الواسعة WAN كشبكة الانترنت، كما يوجد العديد من التجهيزات الخاصة بالوصل بين الأجهزة كالمبدلات Switches والموجّهات Routers، وطريقة الوصول للأجهزة عبر الشبكة تتبع عدة بروتوكولات أشهرها بروتوكول IP (اختصارًا لبروتوكول الإنترنت Internet Protocol) والذي يحدد عنوانًا مميزًا لكل جهاز على الشبكة يمكن الأجهزة من التخاطب فيما بينها، ويمكنك التعرف على أساسيات أخرى والقراءة أكثر عن الأفكار المذكورة في مجال شبكات الحاسوب من سلسلة مقالات أساسيات الشبكات. أساسيات الخوادم الخوادم servers هي كل جهاز أو برنامج يقدم خدمة لعميل، وما يميزه هو القدرة على الاستجابة لكمية مرتفعة من الطلبات، مما يتطلب توفر الموارد الكافية للخادم لأداء عمله كسعة ذاكرة كبيرة او معالج قوي، وسواء كنت مطورًا أو تعمل خصوصًا ضمن فريق العمليات ستحتاج للتعامل مع تلك الخوادم وإعدادها وفهم متطلباتها وطرق التعامل معها، بدءًا من إعداد نظام التشغيل على جهاز الخادم مرورًا بضبط إعداداته لاستضافة الخدمة كتوصيلها بالشبكة وتشغيلها. عملت أكاديمية حسوب على ترجمة أفضل مرجع لتعلم الخوادم وإدارتها وهو كتاب دليل إدارة خوادم أوبنتو. قد تحتاج في هذه النقطة إلى تعلم نظام التشغيل لينكس والتعامل مع سطر الأوامر فيه إن لم تكن تستعمله من قبل لأنه النظام الشائع والأكثر انتشارًا في إدارة الخوادم وتعلمه أساسي لكل من يريد تعلم DevOps ودخول المجال، وهو ما سنذكره في القسم الآتي. وظيفة الخوادم تعتمد معظم البرامج المطورة على التكامل مع خدمات أخرى خارجية لإتمام عملها، فمثلًا تحتاج البرامج لتخزين البيانات واسترجاعها فتعتمد على خادم تخزين قواعد البيانات، أو يعتمد على خادم الويب لإدارة الوصول إليه عبر الويب، والعديد من أنواع الخدمات الأخرى التي يختص بها كل خادم على حدى وسنتعرف على بعض أنواعها في الفقرات التالية. لغات البرمجة في الخوادم تقتصر عملية تنفيذ الأوامر يدويًا ضمن الخادم على المهام البسيطة والتي تُنفذ لمرة واحدة عادةً، لكن أتمتة العمليات على الخادم تتطلب كتابة برامج تعيد تنفيذ المهام المتكررة، ومن تلك اللغات باش Bash وبايثون Python وروبي Ruby وغو Go وغيرها من اللغات والتي يمكنك التعرف عليها وعلى وظيفتها ضمن الخادم من مقال أفضل 5 لغات برمجة لـ DevOps. خوادم الويب يختص خادم الويب بإدارة الوصول للملفات أو التطبيقات على الخادم من قبل العملاء، فهو المسؤول عن تحديد الملف مثلًا الذي يطلبه العميل وإرسال إليه، أو تحديد تطبيق الويب الذي يحاول العميل طلبه وتنفيذه وإرسال النتيجة للعميل، حيث يتم التواصل بين خادم الويب والعميل الذي يطلب تلك الخدمة عبر البروتوكول الشهير HTTP، ومن أشهر استخدامات خادم الويب هو استضافة تطبيقات ومواقع الويب، ومن أشهر خوادم الويب هما خادم أباتشي Apache وإنجن إكس Nginx، ويمكنك الاطلاع أكثر على خادم الويب واستخداماته من مقال مدخل إلى خادم الويب. خوادم قواعد البيانات يختص خادم قواعد البيانات في إدارة تخزين واسترداد البيانات التي تحتاجها التطبيقات الأخرى، فيركز على سرعة وسهولة جلب تلك البيانات بكفاءة عالية وتوفير مزايا للاستعلام ودمج وتنفيذ الحسابات لاستخلاص المعلومات التي يحتاجها التطبيق، فمثلًا ضمن تطبيق ويب للتسوق يتم تخزين بيانات المنتجات وأسعارها وبيانات الطلبات ضمن خادم قاعدة البيانات، وعندما يحتاج تطبيق الويب لجلب أي معلومة ما يسأل خادم قاعدة البيانات عنها، ومن أشهر الخوادم هي MySQL و MongoDB و Postgres، ويمكنك التعرف أكثر على قواعد البيانات من مقال التعامل مع قواعد البيانات. أنواع خوادم أخرى: SSH و Mail و Reverse Proxy إن أردت تعلم DevOps فستحتاج إلى التعامل مع العديد من الخوادم الأخرى، أشهرها خادم SSH والتي تسمح لك بالدخول إلى سطر الأوامر لخادم بطريقة آمنة ويمكنك التعرف عليه أكثر من مقال العمل مع خواديم SSH: العملاء والمفاتيح، وقد يحتاج تطبيقك لإرسال البريد الإلكتروني حيث تفوّض خادم إرسال البريد الإلكتروني Mail Server بتلك العملية، وقد تحتاج أحيانًا لتوزيع الطلبات القادمة إلى خادم معين على عدة خوادم من نفس النوع لتخفيف الحمل عليها، هنا يأتي دور خادم الوكيل العكسي Revese Proxy Server لإدارة التواصل بين العميل وعدة خوادم أخرى، ومن أشهر الخوادم لذلك هي Apache و Nginx. نظام تشغيل لينكس أنظمة التشغيل هي برامج تدير العتاد والبرامج الأخرى التي تعمل على جهاز الحاسوب، لذا واحدة من المهارات الأساسية لك كمطور أو عضو في فريق العمليات DevOps هي التعرف على مكونات نظام التشغيل وطرق التعامل معه لإدارة البرامج وعملها سواء أثناء تطوير البرنامج أو في بيئة الإنتاج، ومن أشهر أنظمة التشغيل نظام لينكس Linux وهو نظام تشغيل مفتوح المصدر يستخدم في معظم الأجهزة والخوادم وحتى حواسب سطح المكتب، ويتوفر منه عدة نسخ يتم تخصيصها لفئة مختلفة من المستخدمين تسمى توزيعات Distributions، لذا فمن المهم التعرف على نظام التشغيل لينكس، ويمكنك البدء بالتعرف على مكوناته وأشهر التوزيعات وطريقة تثبيته من مقال ما هو نظام التشغيل لينكس؟. ويمكنك دومًا الرجوع إلى قسم لينكس ففيه عشرات المقالات المفيدة والذي خصصناه لمن يريد تعلم DevOps بادئ الأمر، وننصح أيضًا بكتاب سطر أوامر لينكس. أساسيات سطر الأوامر يمكنك التخاطب مع أنظمة التشغيل والبرامج عبر الواجهة الرسومية GUI لتشغيل البرامج وضبط الإعدادات وغيرها، لكن هذه الواجهة موجهة للمستخدمين العاديين، لذا دورك كمطور أو عضو من فريق العمليات في DevOps يفرض التعامل مع نظام التشغيل والبرامج لتنفيذ مهام متقدمة لا يمكن أو من الصعب تنفيذها باستخدام الواجهة الرسومية، وهذا هو دور سطر الأوامر وهو استقبال الأوامر من المستخدم وتنفيذها، سواء كانت تلك الأوامر موجهة لنظام التشغيل أو لتنفيذ وضبط برنامج معين يوفر سطر الأوامر مزايا تزيد من كفاءة وقدرة العمليات الممكن تنفيذها. يمكنك من سطر الأوامر تنفيذ البرامج وتمرير المعاملات لها وربط نواتج تنفيذها مع بعضها لتنفيذ مهمة أعقد، ويمكنك من سطر الأوامر إدارة نظام التشغيل نفسه وضبطه، وإدارة الملفات والمجلدات، فقد تحتاج لتنفيذ تلك العمليات بنفسك يدويًا عبر سطر الأوامر، خصوصًا عند إدارة الخوادم فغالبًا لا توفر معظم الخوادم واجهة رسومية فهي موجهة للمختصين فقط كفريق DevOps من مطورين وأفراد عمليات، ويمكنك التعرف على أساسيات سطر الأوامر وأنواعه المختلفة ضمن أنظمة التشغيل من مقال مدخل إلى طرفية لينكس ومقال ما هو سطر الأوامر ؟. تعلم DevOps: التعمق في المجال تطرقنا في القسم السابق المواضيع الأساسية التي يجب على كل مهندس DevOps تعلمها حتى الاحتراف فهي من بدهيات المجال، وسنتطرق في القسم التالي إلى المواضيع الاختصاصية المتقدمة التي ينتقل إليها من يريد تعلم DevOps بعد تعلم الأساسيات وبذلك يطلق عليه مختص DevOps أو مهندس DevOps خبير. الخوادم الافتراضية Virtual Servers والحاويات Containers بدلًا من تخصيص جهاز فيزيائي مخصص لكل خادم نحتاجه، أو لكل نوع نظام تشغيل مختلف نريد تثبيته يمكننا تقسيم الجهاز الواحد إلى عدة أجهزة افتراضية نتعامل معها كما لو كانت أجهزة حقيقية منفصلة بمواردها وأنظمة تشغيلها وبيئتها، لتسهل بذلك عملية نشر البرمجيات أو إعداد البيئة لكل خدمة أو مهمة نريد تنفيذها ضمن ممارسات DevOps، ويوجد العديد من التقنيات والطرق لتقسيم موارد الجهاز وعزلها عن بعضها سنتعرف على كل منها في الفقرات التالية. الحوسبة الافتراضية VM يعتمد مبدأ الحوسبة اعتمادًا على الأجهزة الافتراضية Virtual Machine على تقسيم جهاز الحاسوب بتوزيع العتاد بين الأجهزة الافتراضية، حيث يملك كل خادم افتراضي نظام التشغيل الكامل الخاص به وجزء مخصص له من العتاد Hardware. الخوادم الافتراضية VPS الخادم الافتراضي الخاص VPS (اختصارًا للعبارة Virtual Private Server) هو بيئة افتراضية معزولة خاصة على جهاز فيزيائي معين، ليبدو كأنه خادم منفصل معزول ببيئة وعتاد خاص، بينما قد تتشارك عدة خوادم افتراضية الجهاز نفسه، يفيد ذلك في تقسيم الجهاز الواحد إلى عدة أجهزة منعزلة لاستضافة الخدمات المختلفة أو لتوزيعها على مستخدمين مختلفين. الحاويات containers الحاويات هي طريقة لتحديد البيئة ونظام التشغيل والاعتماديات التي يحتاجها برنامج ما بمعزل عن نظام التشغيل الأساسي، وهي تقنية أخف وأصغر وأسهل بالتعامل معها من الأجهزة الافتراضية VM، حيث أنها تعمل فوق نظام التشغيل الواحد وتتشارك العتاد فيما بينها ولا يتم حجز عتاد مخصص لها، ما يقلل حجمها ويزيد من سرعة تشغيلها، وتعتبر الحاويات جزء مهم في ممارسات DevOps سواء لفريق التطوير لبناء بيئات مختلفة معزولة لاختبار عمل التطبيقات أو لفريق العمليات لنشر تلك الحاويات وإدارتها وتوسيعها، ويمكنك التعرف عليها أكثر من مقال أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات. دوكر Docker دوكر Docker هي منصة لإنشاء وإدارة وتشغيل الحاويات، والتي باستخدامها يمكننا عزل التطبيقات مع اعتمادياتها عن نظام تشغيل الجهاز وتشغيلها على مختلف أنظمة التشغيل كلينكس وويندوز وماك، حيث تُبنى تلك الحاويات من صورة image والتي تحوي داخلها على حالة نظام ما في لحظة زمنية معينة، مثًلا يمكننا بعد تثبيت وإعداد نظام تشغيل ما ثم تثبيت الاعتماديات اللازمة لعمل تطبيق ضمنه ونسخ ملفات ذلك التطبيق وبناءه أن نحفظ تلك اللحظة الزمنية ما قبل تشغيل التطبيق كصورة في دوكر، لنتمكن بعدها من إنشاء حاوية أو أكثر من تلك الصورة لتشغيل التطبيق بداخلها، بحيث ستكون تلك الحاويات معزولة تمامًا عن نظام التشغيل وعن بعضها البعض. تفيد تلك الطريقة في حل الكثير من المشاكل، فمثلًا من الصعب تشغيل تطبيقين على نفس نظام التشغيل يعتمد كل منهما على نسخة مختلفة من اعتمادية ما والذي يؤدي لتعارض في عملها، ولكن باستخدام دوكر يمكننا بناء صورة معزولة لكل من تلك التطبيقات تحوي فقط الاعتماديات التي يريدها كل تطبيق، وبعد إنشاء حاوية لكل من تلك الصور وتشغيلها يصبح وكأنما لدينا جهازين منفصلين يختص كل منهما بتشغيل تطبيق واحد فقط، ويفيد استخدام الحاويات أيضًا في عمليات التوسيع، فمثلًا إذا كان لدينا تطبيق خادم معين يعمل بأقصى طاقة استيعابية له، فالحل يكون بعمل نسخة من ذلك الخادم على جهاز جديد آخر لتوزيع الحمل على كليهما، فهنا نستفيد من سهولة إنشاء خادم جديد آخر في دوكر عبر إنشاء حاوية جديدة فقط من صورة ذلك الخادم ليصبح لدينا خادمين يعملان معًا، وهناك العديد من الحلول الأخرى التي يسمح بها دوكر بدءًا من إنشاء بيئة افتراضية للعمل ضمنها في مرحلة التطوير، مرورًا بالمساعدة في إنشاء بيئة لاختبار أو بناء التطبيقات قبل نشرها. تعتبر دوكر من أشهر منصات إدارة وتشغيل الحاويات وتخدم في معظم ممارسات DevOps سواء من قبل فريق التطوير وخصوصًا من قبل فريق العمليات، وفي أيّ فريق كنت من المهم التعرف على تلك المنصة والتعامل معها ويمكنك البدء بقراءة مقال تعرف على Docker وبمشاهدة فيديو ما هي تقنية Docker؟. كوبرنيتيز Kubernetes توفر منصة كوبرنيتيز Kubernetes (أو اختصارًا K8s) أدوات قوية تختص في إدارة الحاويات وأتمتة عمليات النشر والتوسيع لها سواء ضمن كانت على خادم واحد أو عدة خوادم موزعة، وتوفر أيضًا أدوات أخرى لتوزيع الأحمال بين الحاويات أو لنشر التحديثات عليها مع الحفاظ على استمرار عمل تلك الخدمات، حيث يركز على مهام النشر والتشغيل للخدمات ضمن ممارسات DevOps، لذا وخصوصًا إذا كنت ضمن فريق العمليات من الضروري التعرف على هذه المنصة ويمكنك البدء بقراءة مقال مدخل إلى Kubernetes. دوكر سوارم Docker Swarm يوفر دوكر بديلًا أخف وأبسط من كوبرنيتيز Kubernetes لإدارة الحاويات يدعى دوكر سوارم docker swarm، والذي يحوي مزايا لإدارة تشغيل وتوزيع الحاويات على عدة أجهزة وتوزيع الأحمال تلقائيًا بين الحاويات، لكنه يفتقر لبعض المزايا مثل المراقبة حيث يحتاج للتكامل مع خدمات إضافية لذلك، وعملية التوسعة باستخدامه تكون يدوية على عكس كوبرنيتيز الذي يوفر تلك الميزة تلقائيًا مع العديد من المزايا المتقدمة، لذا يعد دوكر سوارم مناسبًا لحالات الاستخدام البسيطة لإدارة الحاويات والتي لا نحتاج فيها لإعداد وتثبيت أداة جديدة متقدمة مثل كوبرنيتيز. مراقبة الخوادم والتسجيل بعد إعداد الخوادم ونشر التطبيق من الضروري ضمن ممارسات DevOps مراقبة عمله من قبل كلا فريقي التطوير والعمليات، وذلك لمراقبة أي مشاكل محتملة ونشر التطويرات لها لحلها أو التغيير على البنية التحتية للاستجابة للعديد من المتغيرات والحوادث، وفي الفقرات التالية سنتعرف على طرق المراقبة للخوادم وأشهر الأدوات التي تساعدنا في ذلك. مراقبة الخوادم هناك العديد من العوامل الممكن مراقبتها أثناء عمل التطبيق، فمثلًا يجب مراقبة الموارد المستخدمة والموارد المتاحة لتجنب أي خلل في عمل الخادم ورفع أداء الخدمة وسرعتها دومًا، فيمكن ذلك من خلال مراقبة مؤشرات استخدام الذاكرة والمعالج وسعة التخزين على كل خادم، ومن العوامل الأخرى المهمة هي عدد الطلبات المرسلة إلى الخدمة والزمن الوسطي للاستجابة، فقد ترشدنا تلك المؤشرات مثلًا لتوفير عدة نسخ من الخادم نفسه تكفي لاستقبال جميع الطلبات، أو قد تشير إلى خلل ضمن البرنامج ترشد فريق التطوير إلى ضرورة تغيير الخوارزميات المستخدمة ضمن التطبيق لرفع أداءه. ومن العوامل المهم مراقبتها أيضًا هي خرج التطبيق، فكل تطبيق أو خادم يولّد خرجًا يوصف العمليات التي يقوم بها، يُطبع ذلك الخرج إلى الخرج القياسي Standard Output أو إلى ملفات سجلات Logs خاصة، يفيد قراءتها وتحليلها بالحصول على نظرة واسعة على الخوادم أثناء عملها. أشهر أدوات المراقبة توفر أنظمة التشغيل بعض الأدوات المفيدة في مراقبة النظام والبرامج العاملة ضمنه والموارد، وتوجد بعض الأدوات والخدمات الخارجية التي تساعد في مراقبة عدة خوادم معًا: أدوات نظام تشغيل لينكس، أشهرها الأداة top والتي تفيد في معاينة استهلاك كل إجرائية process تعمل حاليًا من موارد الحاسوب، وهي الموجودة افتراضيًا ضمن جميع توزيعات لينكس، وفي حال أردت تفاصيل أكثر يوجد أداة شبيهة بها تدعى htop تعرض لك معلومات مفصلة إضافية، يمكنك التعرف على تلك الأدوات وأخرى متوفرة في لينكس من مقال 4 أدوات مفتوحة المصدر من أجل مراقبة نظام لينكس. أدوات Elastic وهي حزمة متكاملة لمراقبة سجلات التطبيقات والأداء للخوادم وتعد من أشهر الأدوات استخدامًا. أداة Prometheus لتجميع بيانات المراقبة من مختلف المصادر وتخزينها ضمن قاعدة بيانات بتسلسل حدوثها الزمني، ما يمكننا من الاستعلام عن كل تلك البيانات بطريقة سهلة من مصدر واحد، وتوفر هذه الأداة العديد من المزايا المهمة لعملية المراقبة كإرسال التحذيرات بناءًا على البيانات الواردة وغيرها. أداة Grafana والتي توفر لوحة لمراقبة البيانات تحوي مخططات بيانية بالعديد من الأشكال والصيغ وأدوات سهلة للاستعلام عن البيانات الواردة من مختلف المصادر التي يمكن ربطها مع هذه الأداة مثل أداة Prometheus وغيرها. الأداة Checkmk لمراقبة الخوادم والتطبيقات، حيث يتوفر نسخة مفتوحة المصدر منها يمكنك تشغيلها مجانًا، وللتعرف على الأداة وطريقة تثبيتها وإعدادها يمكنك مراجعة المقال مراقبة خادم لينكس باستخدام أداة Checkmk. توضح الصورة التالية نموذجًا عن عمل أداة Grafana: أتمتة سير العمل عبر CI/CD تركز ممارسات DevOps على أتمتة العمليات المتكررة ابتداءً بالعمليات المساعدة لتطوير البرمجيات وصولًا لعمليات النشر، تدخل أنشطة CI/CD كجزء أساسي في ممارسات DevOps وهي ما سنتعرف عليه في الفقرة التالية. التكامل المستمر والنشر المستمر CI/CD التكامل المستمر والنشر المستمر CI/CD (اختصارًا للعبارة Continuous Integration/Continuous Delivery) تدخل في صلب ممارسات DevOps حيث: التكامل المستمر: هي العمليات التي تدخل في مرحلة تطوير البرمجيات لدمج التغييرات والإضافات الصحيحة فقط ضمن الشيفرة المصدرية للتطبيق، كالتدقيق في تنسيق الشيفرة المصدرية لزيادة وضوحها وبناء نسخة من البرنامج وتنفيذ الاختبارات عليها متضمنة التغييرات الجديدة والتأكد من صحة عملها وجهوزية النسخة للنشر النشر المستمر: هي عمليات التجهيز لنشر آخر نسخة من الشيفرة المصدرية التي تنتجها مرحلة التكامل المستمر، وتتكون من بناء البرنامج وتجهيزه لبيئة الإنتاج. يمكنك التعرف أكثر على تلك العمليات من مقال مدخل إلى التكامل المستمر والنشر المستمر CI/CD، وبمتابعة الفيديو التالي: كيف نستعمل Jenkins جنكيز Jenkins هو أحد الأدوات التي تساعد في إنشاء خط pipeline للتكامل والنشر المستمر وأتمتمه جميع العمليات الخاصة بهما، حيث توفر تكاملًا مع العديد من الخدمات الأخرى باستخدام الإضافات plugins، فبدئًا من التكامل مع مستودع الشيفرة المصدرية للبرنامج سواء Github أو Gitlab وصولًا لتحضير بيئات الاختبار والنشر وتنفيذ أوامر المختلفة ضمنها، ويمكنك التعرف على طريقة تثبيته وإعداده من مقال تثبيت Jenkins على أوبنتو 18.04. أتمتة الخوادم والبنية التحتية تصل ممارسات DevOps في أتمتة وتسريع وتسهيل عمليات النشر وإدارة البنية التحتية إلى أتمتة إنشاء البنية التحتية نفسها ولا تصبح مهمة إدارة شكل البنية التحتية مقتصرًا على فريق العمليات، بل يمكن حتى لفريق التطوير المشاركة بتوصيف البنية التحتية التي يلزمها البرنامج قيد التطوير، من هنا نشأت عدة ممارسات منها توصيف البنية التحتية ضمن الشيفرة المصدرية نفسها، فيُضاف ملف خاص ضمن الشيفرة المصدرية للبرنامج يوصّف ما يحتاجه من بنية تحتية، وفي مرحلة النشر تتولى برمجيات خاصة ترجمة ملفات التوصيف تلك إلى عمليات إنشاء وإعداد البنية التحتية لها من خوادم، وتستخدم عملية الأتمتة تلك خصوصًا في البيئة السحابية، ومن الأدوات الشهيرة لذلك Terraform و Puppet و Ansible. Terraform توفر أداة Terraform المفتوحة المصدر طريقة لأتمتة عمليات إنشاء وضبط البنية التحتية في البيئات السحابية، وتقدم طريقة لتحديد النسخ المتلاحقة من أشكال البنية التحتية بشكل إصدارات يمكن التغيير بينها عند الحاجة، وتوفر تكاملًا مع الخدمات السحابية المشهورة مثل خدمات الويب لأمازون AWS. Puppet الأداة Puppet هي من الأدوات الشهيرة أيضًا لأتمتة ضبط الخوادم والبنى التحتية وتستعمل في إدارة الإعدادات والمساعدة في أتمتة الإعداد المُسبق provisioning وإعداد وإدارة البنية التحتية. يقلل التخطيط المسبق واستخدام أدوات إدارة الإعدادات مثل الأداة Puppet من الوقت المستغرق في تكرار المهام الأساسية، ويساعد على التأكد من أن الإعدادات مضبوطة بدقة، وتقوم على أساس واحد فيما بينها في جميع الخوادم والبنى التحتية. وتعمل أداة Puppet، وفق أسلوب خادم-عميل، أي أنك تحتاج لتثبيت برمجية على كل خادم تريد إدارته ليتواصل بدوره مع أداة Puppet لاستقبال أوامر المهام التي تريد تنفيذها على تلك الخوادم. ويمكنك التعرف عليها أكثر من سلسلة مقالات استخدام puppet لإدارة الخواديم. Ansible من عمليات الأتمتة المهمة التي تدخل في ممارسات DevOps هي أتمتة عمليات ضبط الخوادم التي تتكرر، فبدلًا من الدخول يدويًا إلى الخادم وضبط إعداداته عند الحاجة لإنشاء خادم جديد يمكن الاعتماد على أدوات تُنفذ عملية الضبط تلك آليًا مثل الأداة الشهيرة Ansible. أداة أنسبل Ansible من أشهر أدوات أتمتة ضبط الخوادم والتي تمكننا من وصف خطوات تنفيذ مهمة أو ضبط ما للخادم لمرة واحدة ثم إعادة تنفيذها على خادم أو أكثر آليًا، ما يوفر الوقت اللازم لضبط الخوادم ويقلل الأخطاء، فمثلًا يمكنك ترقية نظام التشغيل أو إعداد حسابات المستخدمين والصلاحيات لهم أو نشر تطبيقك أو ترقية قاعدة بيانات على خادم أو بيئة افتراضية أو أكثر عن طريقها دون الحاجة للدخول يدويًا إلى كل منها على حدى وتكرار تنفيذ نفس المهام، ومن أهم مزايا أنسبل أنه لا يحتاج لأي إعداد أو تثبيت لبرامج على الخادم، حيث يتصل ويدخل إلى الخادم ببساطة عبر اتصال SSH لإعدادها تمامًا كما لو كنت تُنفذ تلك المهام يدويًا، ويمكنك التعرف على طريقة تثبيته من مقال كيفيّة تثبيت وضبط Ansible على Ubuntu 18.04. وكما تتميز أنسبل بتوفر المكونات الجاهزة modules لها والتي تحوي على إعدادات مسبقة يمكنك تثبيتها وتنفيذها مباشرة دون الحاجة لكتابتها بنفسك، وللتعرف أكثر على هذه الوحدات وطريقة التعامل معها يمكنك قراءة مقال مدخل إلى وحدات Ansible. خارطة طريق تعلم DevOps إليك خارطة طريق كاملة من الدخول في مجال DevOps حتى احترافه، ومهما كان دورك في عملية صناعة البرمجيات ستستفيد من بعض أو كل هذه المجالات في تطوير كفاءة وسرعة عملك: سوق العمل لديك عدة خيارات وتخصصات عمل في مجال DevOps، كلها تتعلق بالممارسات التي ذكرناها من إدارة للبنية التحتية إلى تطوير أدوات تساعد الفرق التي تعمل على تطوير المنتج ومنها: مهندس DevOps: ستكون مسؤولًا عن التخطيط والتطوير للأدوات المستخدمة في ممارسات DevOps، مثلًا قد يطلب منك تجهيز آلية للاختبار والتوصيل المستمر CI/CD، أو الإشراف على عمليات الأتمتة الخاصة بإصدار نسخ جديدة من المنتج، أو تجهيز نظام مراقبة أداء وأخطاء للمنتج تساعد فرق التطوير والعمليات وحتى الإدارة. مهندس نظم سحابية Cloud Engineer: ستكون مسؤولًا عن إدارة وتطوير الخوادم في بنية سحابية خدمةً للمنتج الرقمي، وإيجاد حلول للمشاكل التقنية التي قد تحدث، كتوسيع النظام حسب الحاجة وزيادة وثوقية وأمان ومرونة النظام بشكل عام، أو قد يطلب منك نقل نظام من استضافة عادية على خادم واحد إلى نظام سحابي للاستفادة من مزاياه، أو تقديم الاستشارات والحلول المتعلقة بتخطيط وإدارة البنية التحتية لنظام ما. ختامًا تعرفنا في هذا الدليل على مفهوم DevOps والممارسات المتعلقة به، كما تعرفنا على الأساسيات المتعلقة به من بنية تحتية وخوادم بأنواعها، والتقنيات المستخدمة كالخوادم الافتراضية والحاويات وأشهر أدوات إنشاءها وإدارتها، كما تعرفنا على عدة أدوات وتقنيات تفيد في أتمتة مختلف المهام في عملية تطوير المنتج الرقمي، كأدوات أتمتة سير عملية التطوير والتوصيل عبر CI/CD وصولًا لأتمتة تحضير وتجهيز البنية التحتية اللازمة للتشغيل. مهما كان دورك في عملية تطوير المنتجات الرقمية فستجد في ممارسات DevOps ومفاهيمها الأساسية ما يساعدك على رفع سرعة تطوير المنتج وجودته وكفاءته، كما ستجد فيها ما يساعدك لرفع انتاجية عملك وكفاءته، فقد تستفيد منها كمطور لتساعدك على أتمتة بعض الأعمال اليدوية كالاختبار وتحضير بيئة تطوير محلية، أو كمسؤول عمليات في أتمتة الكثير من مهام النشر والمراقبة والتحضير لنشر المنتج، أو حتى كمدير منتج لزيادة انتاجية وكفاءة فريق العمل المسؤول عن منتجك وتوفير مراقبة تفيدك في اتخاذ قرارات حول تطوير المنتج. اقرأ أيضًا لماذا تعد DevOps الاستراتيجية التقنية الأكثر أهمية اليوم تحليل حمض DevOps النووي محاكاة عقلية DevOps
-
السلسلة النصية string هي سلسلة من محرف واحد أو أكثر، والمحرف هو إمّا حرف أو رقم أو رمز، وقد ذكرنا في المقال السابق أن السلاسل النصية في لغة روبي تتمثل بكائن، وعلى عكس لغات البرمجة الأخرى تكون السلاسل النصية في روبي قابلةً للتعديل على قيمتها مباشرةً ضمن نفس المتغير، وهي من أكثر أنواع البيانات استخدامًا، إذ تسمح بعرض النصوص التي تفيد مثل طريقة تواصل نصية مع المستخدم، فمثلًا يتألف هذا المقال من سلاسل نصية يُظهرها متصفح الويب على الشاشة. سنتعرف في هذا المقال على طرق التعامل مع السلاسل النصية في لغة روبي بدءًا من طريقة إنشائها وعرضها على الشاشة وتخزينها في متغيرات ودمجها معًا، وسنتعرف أيضًا على طريقة التعامل مع المحارف الخاصة مثل الأسطر الجديدة والفواصل وعلامات الاقتباس المزدوجة double quotes. إنشاء وطباعة السلاسل النصية يمكن تعريف السلاسل النصية في لغة روبي بكتابة مجموعة من المحارف بين علامتي اقتباس مفردة ' أو علامتي اقتباس مزدوجة " على النحو التالي: 'This is a string in single quotes.' "This is a string in double quotes." يمكن استخدام إما علامتي اقتباس مفردة أو مزدوجة، وفي معظم الحالات لا يوجد فرق بينهما، بشرط استخدام نفس العلامة في بداية ونهاية السلسلة النصية؛ وميزة استخدام علامة الاقتباس المزدوجة أنها تسمح بتركيب السلاسل النصية وسنتعرف على تلك الميزة لاحقًا في هذا المقال. يمكن طباعة السلسلة النصية على الشاشة باستخدام التابع print على النحو التالي: print "Let's print out this string." يعرض التابعprint السلسلة النصية كما هي دون إجراء أي تعديل عليها. لنختبر ذلك عن طريق إنشاء ملف روبي جديد بالاسم "print.rb"، ونكتب داخله باستخدام محرر النصوص عدة أوامر طباعة للسلاسل النصية على النحو التالي: print 'This is the first string.' print 'This is the second string.' print 'This is the third string.' نحفظ الملف وننفذ البرنامج: $ ruby print.rb ليظهر الخرج التالي: This is the first string.This is the second string.This is the third string. نلاحظ طباعة كل السلاسل النصية على نفس السطر، ويمكن طباعة كل منها بسطر جديد عن طريق إضافة محرف السطر الجديد يدويًا لنهاية كل منها، أو استخدام التابع puts، الذي يضيف حرف السطر الجديد تلقائيًا في نهاية كل سلسلة نصية. لاختبار ذلك، نعدّل على البرنامج السابق ونبدّل التابع print بالتابع puts على النحو التالي: puts 'This is the first string.' puts 'This is the second string.' puts 'This is the third string.' عند تنفيذ البرنامج نلاحظ الخرج التالي: This is the first string. This is the second string. This is the third string. يطبع التابعputs السلسلة النصية المُمررة له بعد إضافة محرف السطر الجديد تلقائيًا في نهايتها. تخزين السلاسل النصية ضمن متغيرات تشير المتغيرات إلى مكان ما في ذاكرة الحاسوب نسند إليه اسمًا، وتفيد في تخزين البيانات لاسترجاعها لاحقًا، ولتخزين سلسلة نصية ضمن متغيرٍ ما، نعرف اسم المتغير ونسند قيمة السلسلة النصية إليه على النحو التالي: my_string = 'This is my string' بعد تعريف المتغير يمكن الإشارة إليه عن طريق اسمه لتمريره مثلًا إلى تابع ما كما يلي: print my_string لاختبار ذلك ننشئ الملف "string_variables.rb": $ nano string_variables.rb ونضيف التالي: my_name = "Sammy the Shark" my_age = "none of your business" puts my_name puts my_age يُعرّف هذا البرنامج المتغيرين my_name و my_age ويسند لكل منهما قيمة سلسلة نصية، ثم يستخدم التابع puts لطباعة كل سلسلة نصية منهما على سطر منفصل. نحفظ الملف من خلال الضغط على المفتاحين "CTRL + X" ثم على المفتاح "y" ثم ننفذ البرنامج على النحو التالي: $ ruby string_variables.rb ليظهر الخرج التالي: Sammy the Shark none of your business من خلال تعريف المتغيرات وإسناد قيم السلاسل النصية إليها يصبح من السهل التعامل معها وكذلك نتجنب تكرار كتابة نفس السلسلة النصية مرارًا وتكرارًا عند كل استخدام. سنتعرف في الفقرة التالية على طريقة دمج السلاسل النصية معًا لإنشاء سلاسل نصية جديدة. دمج السلاسل النصية يعني دمج السلاسل النصية concatenation دمج سلسلتين نصيتين أو أكثر لإنشاء سلسلة نصية جديدة، ويمكن لتنفيذ ذلك استخدام معامل الدمج الممثل بالرمز + والذي يستخدم أيضًا في عمليات الجمع في العمليات الحسابية، كما في المثال التالي: "sammy" + "shark" لينتج سلسلة نصية جديدة مكونة من دمج تلك السلسلتين: sammyshark لإضافة مسافة بين السلسلتين النصيتين، ينبغي عليك إضافة تلك المسافة ضمن إحدى السلسلتين على النحو التالي: "sammy " + "shark" ويمكن دمج السلاسل النصية مع تلك المُخزنة ضمن المتغيرات بنفس الطريقة: color = "Blue" print "My favorite color is " + color لينتج عن تلك العملية السلسلة النصية "My favorite color is blue"، ونلاحظ إضافة مسافة بنهاية السلسلة النصية الأولى لإضافة مسافة بين النص وقيمة المتغير في النتيجة، ويمكن دمج عدة سلاسل النصية معًا بنفس الطريقة. لنختبر ذلك بإنشاء الملف "concatenation.rb" ونضيف داخله التالي: my_name = "Sammy the Shark" my_age = "none of your business" puts "My name is " + my_name + " and my age is " + my_age + "." يُعرّف هذا البرنامج المتغيرين my_name و my_age، ويُسند لكل منهما قيمة سلسلة نصية كما فعلنا سابقًا، ولكن سنطبع هذه المرة سلسلةً نصيةً مكونةً من دمج عدة قيم مع بعضها، وعند تنفيذ البرنامج يظهر التالي: My name is Sammy the Shark and my age is none of your business. تفيد هذه الطريقة في إدراج قيم المتغيرات داخل السلسلة النصية الجديدة، وينتج عن دمج سلسلتين نصيتين أو أكثر سلسلةً نصيةً جديدةً يمكن استخدامها مثل أي سلسلة نصية أخرى، كأن نسند قيمتها مثلًا لمتغير جديد لاستخدامه لاحقًا: my_name = "Sammy the Shark" my_age = "none of your business" # assign concatenated string to variable output = "My name is " + my_name + " and my age is " + my_age + "." # Print the output. puts output استخدام متغير output إضافي في المثال السابق أمرٌ غير ضروري، إذ تفيد هذه الطريقة في البرامج الأكبر حجمًا، إذ قد تكون هناك حاجة لاستخدامه في أماكن متعددة من البرنامج، كما يُفضّل الفصل بين تنفيذ العمليات على البيانات مثل الدمج والعمليات الحسابية، وبين عملية الطباعة، لأن البرامج مع مرور الوقت ستكبر ولا بُد من تقليل التعقيد فيها، كما يجب التأكد من عدم استخدام معامل الدمج + بين نوعي بيانات مختلفين، فمثلًا لا يمكن دمج سلسلة نصية مع عدد صحيح. دعنا نرى ماذا سيحدث، وننشئ الملف الجديد "strings_and_integers.rb" الذي يحتوي على التالي: my_name = "Sammy the Shark" my_number = 27 print my_name + my_number عرفنا في هذا المثال، المتغير my_name، الذي يحتوي على السلسلة النصية "Sammy the Shark"، والمتغير my_number الذي يحتوي على العدد الصحيح 27، ومن الواضح أن العدد 27 ليس سلسلةً نصيةً لأنه ليس واقعًا بين علامتي اقتباس، وليس عددًا عشريًا أيضًا لأنه لا يحتوي على فاصلة عشرية، فهو من نوع عدد صحيح، وعند تنفيذ البرنامج على النحو التالي: $ ruby strings_and_ints.rb تظهر رسالة الخطأ التالية: strings_and_ints.rb:4:in `+': no implicit conversion of Integer into String (TypeError) from strings_and_ints.rb:4:in `<main>' تعني رسالة الخطأ "no implicit conversion of Integer into String" أنه يمكن دمج سلسلة نصية مع سلسلة نصية فقط، ولا يمكن دمج عدد صحيح مع سلسلة نصية مباشرةً. سيظهر عند تنفيذ البرنامج في إصدار روبي رقم Ruby 2.3 وما قبل رسالة الخطأ التالية بدلًا من ذلك: strings_and_ints.rb:4:in `+': no implicit conversion of Fixnum into String (TypeError) from strings_and_ints.rb:4:in `<main>' ملاحظة: في الإصدارات السابقة من روبي كنا نعبّر عن الأعداد الصحيحة باستخدام الكلمة المفتاحية Fixnum (اختصارًا للعبارة Fixed Number) وفي إصدار Ruby 2.4 استُبدل كل من Fixnum ونظيرتها Bignum بالكلمة Integer. يمكننا حل المشكلة بتعديل البرنامج السابق وإضافة علامتي اقتباس حول الرقم 27 ليصبح "27" لتعريفه على أنها سلسلة نصية بدلًا من عدد صحيح، أو يمكننا تحويل العدد إلى سلسلة نصية باستدعاء التابع to_s منه عند إنشاء السلسلة النصية كما يلي: my_name = "Sammy the Shark" my_number = 27 print my_name + my_number.to_s يحول التابع to_s. العدد الصحيح إلى سلسلة نصية، وتُعد تلك الطريقة أفضل، إذ يمكن الاحتفاظ بالعدد على أنه عدد صحيح داخل البرنامج كما هو، وتحويله إلى سلسلة نصية عند الحاجة فقط، أي أثناء طباعته مثلًا، وبذلك يمكن استخدامه على أنه عدد صحيح في أماكن أخرى من البرنامج، وعند تنفيذ البرنامج مرةً أخرى سنلاحظ طباعة السطر "Sammy the Shark27". تُعد عملية تحويل الأرقام إلى سلاسل نصية لدمجها معًا من العمليات التي نحتاجها بصورةٍ متكررة عند التعامل مع مختلف أنواع البيانات، مثل أرقام الهواتف والقيم النقدية وعناوين البريد وغيرها من البيانات التي نحتاج لعرضها على الشاشة ضمن سلسلة نصية. عملية الدمج مهمة جدًا، ولكن استخدامها قد يكون صعبًا أحيانًا، فإذا نسينا إضافة معامل الجمع + عن طريق الخطأ بين كل زوج من السلاسل النصية التي نحاول دمجها فسينتج خطأ في الصياغة عند تنفيذ البرنامج، ويجب تذكر تحويل الأعداد إلى سلاسل نصية قبل دمجها، لهذا السبب توفر روبي طريقةً أخرى لإدراج قيم المتغيرات داخل السلاسل النصية تدعى تضمين السلاسل string interpolation والتي تحل كلتا المشكلتين وسنتعرف عليها في الفقرة التالية. التضمين في السلاسل النصية ينتج عن دمج السلاسل النصية والمتغيرات معًا باستخدام معامل الدمج + عبارة برمجية صعبة القراءة والفهم، ومن هنا تأتي ميزة التضمين في السلاسل النصية، إذ تسمح بإدراج العبارات البرمجية داخل سلسلة نصية واحدة محاطة بعلامتي اقتباس مزدوجة حصرًا، فبدلًا من دمج القيم على النحو التالي: "My name is " + my_name + "!" يمكننا استخدام ميزة التضمين كما يلي: "My name is #{my_name}!" نلاحظ أنه وبدلًا من إنهاء السلسلة النصية واستخدام معامل الجمع + لدمجها بقيمة أخرى، نُضمّن قيمة المتغير مباشرةً داخل السلسلة النصية باستخدام الصيغة {}#، وفيها نخبر روبي بتقييم التعبير البرمجي داخلها ودمجه في ذلك المكان من السلسلة النصية. لنختبر ذلك بإنشاء ملف برنامج جديد بالاسم "interpolation.rb" ونضيف ما يلي: my_name = "Sammy the Shark" my_age = "none of your business" output = "My name is #{my_name} and my age is #{my_age}." puts output وهو نفس البرنامج الذي كتبناه سابقًا ولكن باستخدام التضمين لإنشاء النتيجة النهائية. يتيح التضمين ميزةً أخرى وهي تحويل القيم الرقمية إلى سلاسل نصية تلقائيًا قبل دمجها، ولاختبار ذلك نعود إلى الملف "strings_and_integers.rb" الذي كتبناه سابقًا ونغير السطر الأخير ليصبح على النحو التالي: my_name = "Sammy the Shark" my_number = 27 # use interpolation instead of concatenation print "My name is #{my_name} and my favorite number is #{my_number}." ستحوّل روبي المتغير my_number إلى سلسلة نصية تلقائيًا، ونلاحظ ذلك عند تشغيل البرنامج والذي سيطبع النتيجة التالية دون حدوث أخطاء: My name is Sammy the Shark and my favorite number is 27. نلاحظ قوة ميزة التضمين في السلاسل النصية فهي الطريقة المفضلة دومًا لدمج السلاسل النصية مع قيم المتغيرات الأخرى بسهولة في التعامل والقراءة. صياغة السلسلة النصية وقيمتها نلاحظ أن جميع السلاسل النصية التي أنشأناها محاطةٌ بعلامات الاقتباس ضمن الشيفرة البرمجية، ولكن الناتج النهائي عند الطباعة لا يتضمن علامات الاقتباس، ويوجد فرق بين الاشارة إلى السلاسل النصية في الشفرة البرمجية وما يُطبع، فالسلسلة النصية المكتوبة في الشيفرة البرمجية بما في ذلك علامات الاقتباس هي صياغة السلسلة النصية، بينما النتيجة التي تُعرض للمستخدم دون علامات الاقتباس بعد معالجة تلك القيمة هي قيمة تلك السلسلة. يمثل ما يلي صياغة لسلسلة نصية: "Sammy the Shark" وقيمة السلسلة النصية تلك هي العبارة Sammy the Shark، ويبرز الفرق بين صياغة السلسلة النصية وقيمتها عند استخدام المحارف الخاصة مثل علامات الاقتباس أو الفواصل ضمن السلسلة النصية، وسنتعرف على طريقة إدراجها في الفقرة التالية. إدراج علامات الاقتباس ضمن السلاسل النصية تُستخدم علامات الاقتباس المفردة والمزدوجة ضمن لغة روبي لتحديد بداية ونهاية السلسلة النصية، لذلك لا يمكن استخدامها ضمن قيمة السلسلة النصية نفسها، فإذا حاولنا مثلًا استخدام علامة اقتباس مفردة ضمن سلسلة نصية محاطة بعلامات اقتباس مفردة سيحدث خطأ: 'This isn't what I wanted.' إذ ستفهم لغة روبي علامة الاقتباس المفردة ضمن الكلمة isn't بأنها نهاية السلسلة النصية، وقبلها وما بعدها هو شيفرة روبي وليس تكملة للنص مما سينتج عنه خطأ، وتنطبق نفس الحالة على استخدام علامات الاقتباسات المزدوجة في سلسلة نصية محاطة بعلامات اقتباس مزدوجة كما هو الحال في المثال التالي: "Sammy says, "Hello!"" ففي هذا المثال تنتهي السلسلة النصية عند علامة الاقتباس المزدوجة الأولى قبل الكلمة Hello، وتبدأ شيفرة روبي اعتيادية بعدها مما يؤدي إلى حدوث خطأ. لتجنب هذه المشكلة لدينا عدة خيارات؛ إذ يمكن استخدام علامات الاقتباس المقابلة لعلامة الاقتباس التي نريد إدراجها ضمن النص، فمثلًا عند الحاجة إلى استخدام علامات الاقتباس المزدوجة ضمن السلسلة النصية، نُعرف السلسلة النصية باستخدام علامات اقتباس مفردة والعكس صحيح؛ أو يمكن استخدام محارف الهروب؛ أو استخدام صيغ مختلفة لإنشاء السلاسل النصية في روبي سنتعرف على كل من تلك الطرق على حدى في الفقرات التالية. الخيار الأول: استخدام علامات الاقتباس المقابلة أسهل طريقة لإدراج علامات الاقتباس ضمن السلاسل النصية هي استخدام علامات الاقتباس المقابلة للعلامة المُدرجة، فعندما تحتوي السلسلة النصية على علامات اقتباس مزدوجة، نستخدم العلامات المفردة لتعريف السلسلة النصية، وعندما تحتوي السلسلة النصية على علامات اقتباس مفردة نستخدم علامات الاقتباس المزدوجة لتعريف السلسلة النصية، فبدلًا من تعريف السلسلة النصية التالية بعلامات الاقتباس المفردة: 'This isn't what I wanted.' سنعرفها باستخدام علامات اقتباس مزدوجة: "This isn't what I wanted." وبدلًا من تعريف القيمة التالية باستخدام علامات اقتباس مزدوجة: "Sammy says, "Hello!"" سنستخدم علامات الاقتباس المفردة: 'Sammy says, "Hello!"' لا يمكن الاعتماد على هذا الحل دومًا، سوى عند استخدام نوع واحد من علامات الاقتباس ضمن السلسلة النصية، أما عند استخدام كلا النوعين كما هو واضح في المثال التالي: "Sammy says, "I'm a happy shark!"" في هذا المثال، تنتهي السلسلة النصية عند علامة الاقتباس المزدوجة الأخيرة قبل I'm، ومن ثم تبدأ روبي في قراءة الكلمة I'm والتي تبدأ فيها بسلسلة نصية جديدة بقيمة ""!m a happy shark، ولكن هذه السلسلة النصية الجديدة لا تحتوي على علامة اقتباس مفردة تُنهيها، وعند محاولة استخدام علامات الاقتباس المفردة لتعريف السلسلة النصية تُضمّن علامة اقتباس مفردة داخل النص مما يؤدي إلى نفس المشكلة: 'Sammy says, "I'm a happy shark!"' تنتهي السلسلة النصية هذه المرة عند علامة الاقتباس المفردة بعد كلمة I'm، واللجوء لهذا الخيار دومًا يجعل السلاسل النصية ضمن البرنامج الواحد غير متناسقة، فتارةً يجب استخدام العلامات المفردة وتارةً أخرى المزدوجة. لحل تلك المشكلة يمكننا استخدام طريقة محارف الهروب Escaping characters التي سنتعرف عليها في الفقرة التالية. الخيار الثاني: استخدام محارف الهروب في السلاسل النصية يمكن استخدام محارف الهروب ضمن السلاسل النصية وذلك بوضع خط مائل عكسي \ قبلها، لإخبار روبي بأن المحرف التالي لها على هو جزء من السلسلة النصية نفسها. لتوضيح فائدة تلك الطريقة سنحاول معالجة النص التالي الذي يحتوي على علامات اقتباس مزدوجة ومفردة داخله: "Sammy says, "I'm a happy shark!"" ننشئ ملف جديد بالاسم "quoting.rb" بداخله التالي: print "Sammy says, "I'm a happy shark!"" ننفذ البرنامج: $ ruby quoting.rb ونلاحظ ظهور خطأ بسبب وجود علامات الاقتباس المزدوجة داخل السلسلة النصية: quoting.rb:1: syntax error, unexpected tCONSTANT, expecting end-of-input print "Sammy says, "I'm a happy shark!"" ^ لحل تلك المشكلة نضع الخط المائل العكسي \ قبل كل علامة اقتباس مزدوجة داخل النص كما يلي: print "Sammy says, \"I'm a happy shark!\"" وننفذ البرنامج مرة أخرى ونلاحظ طباعة النص دون أي مشاكل: Sammy says, "I'm a happy shark!" نلاحظ بأننا لم نكن بحاجة للهروب من علامات الاقتباس المفردة في هذا المثال، وذلك لاستخدامنا علامات مزدوجة لتعريف السلسلة النصية، لذا يجب فقط الهروب من علامات الاقتباسات التي تفهمها روبي خطأً. يمكننا الاستغناء عن محارف الهروب من علامات الاقتباسات تمامًا وذلك باستخدام صيغة مختلفة لتعريف السلاسل النصية سنتعرف عليها في الخيار الثالث في الفقرة التالية. الخيار الثالث: الصيغة البديلة لتعريف السلاسل النصية استخدمنا حتى الآن علامات الاقتباس لتعريف السلاسل النصية، وتوفر روبي طريقةً أخرى لإنشاء سلاسل نصية باستخدام محارف أخرى، إذ يمكننا تعريف محرف فاصل delimiter أو محرف غير موجود ضمن السلسلة النصية وأن نحيط به قيمة السلسلة النصية بدلًا من علامات الاقتباس، وندل روبي على المحرف الذي اخترناه بذكره بعد محرف النسبة المئوية %، ففي المثال التالي اخترنا المحرف $ لتعريف السلسلة النصية: %$Sammy says, "I'm a happy shark!"$ تهرب هذه الصيغة تلقائيًا من المحارف الخاصة ضمن السلسلة النصية وينتج عن السلسلة النصية السابقة ما يلي: "Sammy says, \"I'm a happy shark!\"" ويجب الانتباه إلى تغيير المحرف الفاصل في حال وجوده ضمن السلسلة النصية، فمثلًا في المثال السابق إذا احتوى النص على المحرف $ فيجب الهروب بإضافة المحرف \ قبله ضمن النص، أو يمكن استخدام زوج من الأقواس المعقوصة Curly braces { و } لتحديد بداية ونهاية السلسلة النصية، أو الأقواس المعقوفة square braces [ و ]، أو الأقواس الهلالية parentheses ( و )، ولكن الأقواس المعقوصة هي الأكثر شيوعًا: %{Sammy says, "I'm a happy shark!"} تدعم كل تلك الصيغ ميزة تضمين القيم داخل السلسلة النصية: droplets = 5 print %{Sammy says, "I just created #{droplets} droplets!"} تُستخدم الصيغ {}Q% و {}q% أحيانًا أيضًا لتعريف السلاسل النصية في روبي، وتعمل صيغة {}Q% بالضبط مثل صيغة علامات الاقتباس المزدوجة، ما يعني عدم الحاجة للهروب من علامات الاقتباس المزدوجة داخل السلسلة النصية وإمكانية تضمين القيم داخل السلسلة النصية: droplets = 5 print %Q{Sammy says, "I just created #{droplets} droplets!"} أما صيغة {}q% فهي بديلة لصيغة علامات الاقتباس المفردة: %q{Sammy says, "I'm a happy shark!"} يُستخدم أحيانًا مع الصيغتين {}q% و {}Q% الأقواس الهلالية أو الأقواس المعقوفة بدلًا من الأقواس المعقوصة، وكما نلاحظ توفر روبي العديد من الطرق لإنشاء السلاسل النصية ويفضل استخدام صيغة تعريف موحدة ضمن الشيفرة ككل، وتفي غالبًا كلًا من الصيغتين {}Q% و {}q% بالغرض. الآن، بعد أن تعرفنا على طرق التعامل مع المحارف الخاصة سنتعرف في الفقرة التالية طريقة التعامل مع السلاسل النصية الطويلة والتي تمتد إلى عدة أسطر. السلاسل النصية الطويلة والأسطر الجديدة لإدراج سطر جديد ضمن سلسلة نصية ما يمكن استخدام محرف السطر الجديد n\، أو المحرف r\ الذي يدل على عودة المؤشر لبداية السطر وهو مفهوم أتى من أنظمة ويندوز القديمة التي كانت تحاكي طريقة الكتابة في الآلات الكاتبة اليدوية: output = "This is\na string\nwith newlines" puts output عند طباعة السلسلة النصية السابقة نلاحظ النزول إلى سطر جديد عند كل ورود للمحرف n\: This is a string with newlines توفر لنا هذه الطريقة إمكانية طباعة نص متعدد الأسطر، ولكن في حال كان النص طويلًا سيمتد على سطر واحد طويل وسيصعب قراءته والتعامل معه، ويمكن حل تلك المشكلة بعدة طرق، إذ يمكن استخدام ميزة دمج السلاسل النصية لكتابة النص الطويل على أجزاء كلًّا بسطر ضمن الشيفرة المصدرية للبرنامج: output = "This is a\n" + "longer string\n" + "with newlines." puts output يمكن أيضًا وضع الأسطر الجديدة مباشرةً داخل النص المكتوب دون الحاجة لاستخدام المحارف الخاصة على النحو التالي: output = "This is a longer string with newlines" puts output وهذه الطريقة ممكنة أيضًا عند استخدام أي صيغة بديلة لتعريف السلاسل النصية: output = %{This is a longer string with newlines} puts output نلاحظ عدم الحاجة لاستخدام المحرف n\ لإدراج سطر جديد، كما أن هذه الطريقة ستحافظ على المسافات البيضاء متضمنةً بداية الأسطر والأسطر الجديدة أيضًا: This is a longer string with newlines ولمنع ذلك يجب إزالة المسافات البيضاء الإضافية في بداية الأسطر ضمن الشيفرة كي لا تكون جزءًا من النص نفسه كما يلي: output = %{This is a longer string with newlines } يمكن أيضًا إنشاء سلاسل نصية متعددة الأسطر باستخدام صيغة تسمى heredoc كما يلي: output = <<-END This is a longer string with newlines END تدل العلامتان END->> و END على بداية ونهاية السلسلة النصية وتحافظ هذه الصيغة على المسافات البيضاء في بداية الأسطر، فإذا أردنا كتابة شيفرة برمجية مُنسقة مثلًا بداخلها فتنسيق تلك الشيفرة سيبقى كما هو وتبقى واضحة القراءة كما في المثال التالي: output = <<-END This is a longer string with newlines END سيُحتفظ بالمسافتين في بداية كل سطر في المثال السابق. تتوفر في روبي 2.3 والإصدارات الأحدث صيغةً تسمى "squiggly heredoc"، والتي تزيل تلك المسافات تلقائيًا من بداية الأسطر، وصيغتها تكون بتبديل الشرطة hyphen - بالعلامة تيلدا tilde ~ لتصبح بداية السلسلة النصية هي ~>> بدلًا من ->> على النحو التالي: output = <<~END This is a longer string with newlines and the code is indented but the output is not. END لينتج لدينا الخرج التالي: This is a longer string with newlines and the code is indented but the output is not. تدعم هذه الطريقة في روبي أيضًا ميزة تضمين قيم المتغيرات داخل السلاسل النصية، وكما لاحظنا هناك العديد من الطرق للتعامل مع الأسطر الجديدة والسلاسل النصية متعددة الأسطر في روبي، ويُفضل توحيد استخدام إحدى تلك الطرق ضمن الشيفرة البرمجية. تكرار السلسلة النصية نحتاج أحيانًا لتكرار سلسلة نصية ما عدة مرات، وتوفر روبي لذلك معامل الضرب *، والذي على العكس من معامل الجمع + فيمكن استخدامه مع الأعداد الصحيحة لتحديد مرات تكرار السلسلة النصية التي تسبقه، فمثلًا لطباعة السلسلة النصية "Sammy" تسع مرات يمكن ضرب هذه السلسلة النصية بالعدد 9 على النحو التالي: print "Sammy" * 9 لينتج الخرج التالي: SammySammySammySammySammySammySammySammySammy تفيد هذه الميزة للرسم باستخدام المحارف، لنرسم صندوقًا يحيط بعبارة ما على النحو التالي: puts "=" * 15 puts "| Hello World |" puts "=" * 15 نلاحظ كيف استفدنا من تكرار السطر العلوي والسفلي لرسم مربع يحيط بالعبارة والتي سينتج عنها التالي: =============== | Hello World | =============== الخاتمة تعرفنا في هذا المقال على السلاسل النصية في لغة روبي بالتفصيل، والطرق المختلفة للتعامل معها بدءًا من إنشاءها وصولًا إلى دمجها معًا وإدراج القيم بداخلها وطرق كتابة المحارف الخاصة مثل علامات الاقتباس فيها والأسطر الجديدة نهايةً بتكرار السلسة النصية لعدد من المرات، إذ تُعد السلاسل النصية من الأفكار المهمة في لغات البرمجة ويجب احتراف التعامل معها لحل العديد من المشاكل وتطوير البرامج. لمزيد من التفصيل، ارجع إلى صفحة النوع String في موسوعة حسوب. ترجمة -وبتصرف- للمقال How To Work with Strings in Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: أنواع البيانات في لغة روبي كتابة أول برنامج بلغة روبي السلاسل والمتغيرات في روبي
-
تُصنف البيانات في لغات البرمجة إلى عدة أنواع يستدل الحاسوب بها للتعامل مع تلك البيانات أثناء تنفيذ البرامج، مثل تحديد العمليات الممكن تنفيذها على تلك البيانات، وهي تشبه إلى حد كبير أنواع البيانات التي نتعامل معها في حياتنا العادية، مثل أنواع الأعداد في الرياضيات، إذ توجد الأعداد الطبيعية مثل 0 و1 و2، والأعداد الصحيحة مثل -1 و0 و1، والأعداد غير الكسرية أو غير النسبية irrational numbers مثل π، بحيث يمكن جمع الأعداد من تلك الأنواع المختلفة مع بعضها لنحصل على نتيجة معينة، فمثلًا يمكن جمع العدد الطبيعي 5 مع العدد غير الكسري π: 5 + π يمكننا إبقاء المعادلة كما هي لإظهار العدد غير الكسري π، أو يمكننا تقريبه بعدد محدود من الأرقام العشرية ثم جمع تلك القيمتين معًا على النحو التالي: 5 + π = 5 + 3.14 = 8.14 لكن لا يمكن جمع نوعين مختلفين من البيانات، مثل الكلمات والأعداد مثلًا معًا: sky + 8 لا يمكن حل مثل تلك المعادلات وبالتالي لا يستطيع الحاسوب حلها أيضًا، فلا يمكن جمع بيانات من نوع كلمة مثل الكلمة "Sky" مع عدد طبيعي مثل العدد 8، لذا يجب الانتباه لنوع البيانات عند إسنادها لمتغيرات أو التعديل عليها وتنفيذ العمليات بينها مثل الجمع والطرح والضرب وغيرها. سنتعرف في هذا المقال على أهم أنواع البيانات الموجودة في لغة روبي Ruby، مثل الأعداد الصحيحة والعشرية والسلاسل النصية والرموز والمصفوفات وجداول Hash، وسنتعرف بعدها على مفهوم ديناميكية الأنواع dynamic typing والذي تعتمد عليه لغة روبي لتحديد نوع البيانات المُخزّنة ضمن المتغيرات والذي من الضروري فهمه لتجنب الأخطاء التي قد تقع خصوصًا أن المتغيرات يمكن أن تحتوي على قيمة من أي نوع من أنواع البيانات. سنتعرف في الفقرة التالية على أول نوع من البيانات وهو الأعداد الصحيحة وطرق التعامل معها في لغة روبي. الأعداد الصحيحة integers تشبه الأعداد الصحيحة integers في لغة روبي الأعداد الصحيحة في الرياضيات تمامًا، بحيث يمكن أن تحتوي على قيم أعداد موجبة أو سالبة أو تساوي الصفر، مثل الأعداد"1-" و"0" و"1"، ويمكن طباعتها على النحو التالي: print -25 ليكون الخرج ببساطة: -25 ويمكن تخزين العدد الصحيح ضمن متغير بإسناد قيمته إليه ثم طباعة ذلك المتغير على النحو التالي: my_int = -25 print my_int ويكون الخرج: -25 يمكن تنفيذ العمليات الحسابية على الأعداد الصحيحة، مثل الجمع بين عددين وطباعة النتيجة على النحو التالي: sum = 116 - 68 print sum ويكون الخرج على النحو التالي: 48 ندرج عادةً الفاصلة "," بعد كل ثلاث خانات من الأعداد الكبيرة لتسهيل قراءتها فمثلًا نكتب العدد مليون "1000000" بإدراج الفواصل كما يلي: "1,000,000"، أما في لغة روبي لا يمكن استخدام هذه الفاصلة ضمن العدد، وتُستخدم علامة الشرطة السفلية _ التي توفرها روبي لنفس الغرض وهو تسهيل قراءة الأعداد الكبيرة على النحو التالي: large_number = 1_234_567 print large_number نلاحظ عند طباعة قيمة ذلك العدد لن تُطبع الشرطة السفلية _، وستُطبع قيمة العدد الصحيح فقط: 1234567 سنتعرف في الفقرة التالية على نوع جديد من البيانات وهو الأعداد العشرية وطريقة التعامل معها. الأعداد العشرية floating-point number تُمثل الأعداد العشرية floating-point number أعدادًا حقيقية مثل الأرقام الكسرية أو غير الكسرية، وهي التي تحتوي على جزء عشري مثل العدد "9.0" أو "116.42-". يمكن طباعة الأعداد العشرية في لغة روبي تمامًا مثل طريقة طباعة الأعداد الصحيحة على النحو التالي: print 17.3 ويكون الخرج على النحو التالي: 17.3 يمكن أيضًا تعريف متغير وإسناد قيمة عشرية إليه كما يلي: my_float = 17.3 print my_float ليعطي الخرج التالي: 17.3 وكما هو الحال مع الأعداد الصحيحة، يمكن تنفيذ العمليات الحسابية على الأعداد العشرية أيضًا على النحو التالي: sum = 564.0 + 365.24 print sum ويكون الخرج: 929.24 ناتج جمع عدد عشري مع عدد صحيح في لغة روبي هو عدد عشري: sum = 564 + 365.24 print sum ويكون الخرج: 929.24 سنتعرف في الفقرة التالية على نوع مهم من أنواع البيانات في لغة روبي وهي البيانات البوليانية. البيانات البوليانية boolean تُستخدم أنواع البيانات البوليانية boolean لتمثيل القيم المتعلقة بالمنطق الرياضي والتي تُستخدم لتطوير الخوارزميات في علم الحاسوب، إذ يُمثّل هذا النوع من البيانات في لغة روبي إما بالقيمة الصحيحةtrue أو القيمة الخاطئةfalse، إذ تنتج تلك القيم عن العديد من العمليات الرياضية مثل العمليات البوليانية التالية: أكبر من 500 > 100 صحيح true 1 > 5 خاطئ false أصغر من 200 < 400 صحيح true 4 < 2 خاطئ false المساواة 5 = 5 صحيح true 500 = 400 خاطئ false كما هو الحال أنواع البيانات السابقة، يمكننا تخزين القيم المنطقيةtrue أو false ضمن متغير: result = 5 > 8 نستخدم التابع()print كما فعلنا سابقًا لطباعة قيمة المتغير البولياني: print result بما أن العدد 5 ليس أكبر من العدد 8، ستظهر النتيجة التالية: false البيانات البوليانية من أنواع البيانات الشائع استخدامها في لغة روبي ومعظم اللغات البرمجية الأخرى، وسنتعامل معها في هذه السلسلة وسنرى كيف يمكن أن تعيد التوابع والعمليات المختلفة القيم إما true أو false وكيف نستخدم هذه القيم للتحكم بمسار تنفيذ البرنامج، وسنتعرف في الفقرة التالية على السلاسل النصية وطريقة التعامل معها. السلاسل النصية strings تتكون السلاسل النصية strings من سلسلة من محرف واحد أو أكثر، والمحرف هو أي حرف أو رقم أو رمز، ويمكننا تعريف سلسلة نصية في لغة روبي بكتابة قيمتها ضمن علامتي اقتباس مفردة ' أو مزدوجة " على النحو التالي: "This is a string in double quotes." استخدمنا في مقال سابق السلاسل النصية عند كتابة أول برنامج بلغة روبي لطباعة العبارة "!Hello, World" على الشاشة، إذ أن تلك القيمة هي سلسلة نصية تتكون من مجموعة من المحارف: print "Hello, World!" كما هو الحال مع الأنواع الأخرى من البيانات، فيمكن تخزين السلاسل النصية أيضًا في متغيرات: output = "Hello, World!" ويمكن طباعة النص المخزن في المتغير باستخدام اسم المتغير على النحو التالي: print output ليكون الخرج: Hello, World! يمكن تنفيذ العديد من العمليات على السلاسل النصية -كما هو الحال بالنسبة للأرقام- للتعديل عليها، وتكمن أهمية هذا النوع من البيانات بأنه طريقة للتواصل النصي بين المستخدم والبرنامج. سنتعرف في الفقرة التالية على نوع جديد من البيانات وهو المصفوفات التي تفيدنا في التعامل مع قائمة من البيانات. المصفوفات arrays المصفوفة array هي نوع من البيانات يُمكّننا من تخزين مجموعة من القيم داخله نسميها عناصر تلك المصفوفة، ويمكن تعريف المصفوفة بكتابة قيم عناصرها بين أقواس مربعة [ ] والفصل بينها بالفاصلة ,، فمثلًا لتعريف مصفوفة من الأرقام الصحيحة نكتب التالي: [-3, -2, -1, 0, 1, 2, 3] أو مصفوفة من الأعداد العشرية: [3.14, 9.23, 111.11, 312.12, 1.05] أو مصفوفة من السلاسل النصية: ['shark', 'cuttlefish', 'squid', 'mantis shrimp'] وكما هو الحال مع أنواع البيانات الأخرى يمكننا تخزين قيمة المصفوفة ضمن متغير: sea_creatures = ['shark', 'cuttlefish', 'squid', 'mantis shrimp'] وعند طباعة قيمة أي مصفوفة: print sea_creatures سيظهر شكلها تمامًا كما هي عند تعريفها: ['shark', 'cuttlefish', 'squid', 'mantis shrimp'] يمكن الوصول إلى عنصر من عناصر أي مصفوفة باستخدام رقم الفهرس لذلك العنصر، وهو عدد يشير لترتيب ذلك العنصر ضمن المصفوفة ويبدأ من الصفر: puts sea_creatures[0] # shark puts sea_creatures[2] # squid للوصول إلى قيمة آخر عنصر من المصفوفة نستخدم رقم الفهرس السالب 1-، كما توفر لغة روبي التابعfirst.، الذي يعيد أول عنصر من المصفوفة، والتابع last. الذي يعيد آخر عنصر منها: puts sea_creatures.first # shark puts sea_creatures.last # mantis shrimp يمكن لعناصر المصفوفة الواحدة أن تكون بأنواع مختلفة من البيانات، فمثلًا يمكن تخزين سلاسل نصية ورموز وحتى مصفوفات أخرى ضمن مصفوفة واحدة: record = [ :en, "Sammy", 42, [ "coral", "reef" ] ] تتمتع المصفوفات في لغة روبي بخاصية التعديل؛ أي يمكن إضافة عناصر جديدة إليها، أو حذف عناصر منها، أو تعديل قيم العناصر المخزنة فيها. سنتعرف في الفقرة التالية على نوع جديد من البيانات وهو الرموز والتي تساعدنا في تسمية الأشياء ضمن البرنامج. الرموز Symbol الرمز Symbol هو نوع خاص من البيانات يُستخدم مثل اسم أو معرّف داخل برنامج روبي، وتتميز الرموز بأن قيمتها ثابتة ولا يمكن تعديلها بعد تعريفها، وتشبه طريقة تعريف الرمز طريقة تعريف أي متغير ولكن دون تعيين قيمة له وإضافة محرف النقطتين : قبل اسمه على النحو التالي: :time_zone نستخدم الرموز في لغة روبي لتسمية بعض الأشياء الهامة ضمن البرنامج، بينما نستخدم السلاسل النصية للنصوص التي نحتاج لتعديلها أو العمل عليها، إذ أن كل سلسلة نصية في روبي هي كائن جديد منفصل مخزن ضمن موقع منفصل في الذاكرة حتى لو تطابقت قيمها مع بعضها، بينما تتشارك الرموز المتطابقة بالقيمة بكونها كائن واحد مخزن في نفس المكان في الذاكرة. سنتعرف أكثر على استخدامات الرموز في الفقرة التالية عندما نتعرف على جداول Hash والتي تسمح لنا بربط مفاتيح بقيمها. جداول Hash جدول Hash هو مجموعة من أزواج المفاتيح والقيم تفيد في تخزين البيانات وتسمية كل منها وتسهيل الوصول إليها، ونستخدمها عادةً لتخزين بيانات متعلقة بشيء ما مثل بيانات شخص أو مستخدم، وتكون صيغة تعريفها على النحو التالي: {"first_name" => "Sammy", "last_name" => "Shark"} يمكن تخزين جدول Hash ضمن متغير بنفس طريقة إسناد الأنواع الأخرى من البيانات: user = {"first_name" => "Sammy", "last_name" => "Shark"} للوصول لقيمة ما من جدول Hash، نستخدم اسم المفتاح الخاص بتلك القيمة كما يلي: print user["first_name"] # "Sammy" print user["last_name"] # "Shark" يمكن استخدام الرموز مثل مفاتيح للقيم ضمن جدول Hash بدلًا من السلاسل النصية على النحو التالي: user = {:first_name => "Sammy", :last_name => "Shark"} يفضل دائمًا استخدام الرموز مثل مفاتيح للجداول لتقليل استخدام الذاكرة وتحسين الأداء، نظرًا لأن كل الرموز المتساوية بالقيمة تتشارك الكائن نفسه والقيمة نفسها ضمن الذاكرة، بينما سيولد لكل سلسلة نصية كائن جديد وفريد وبمكان جديد في الذاكرة، ويمكن الوصول للقيمة المقابلة لرمز ما ضمن جدول Hash باستخدام الرمز نفسه على النحو التالي: print user[:first_name] # "Sammy" print user[:last_name] # "Shark" يمكن تعريف جدول Hash بالصيغة التالية أيضًا: user = {first_name: "Sammy", last_name: "Shark"} تشبه هذه الصيغة الصيغة المستخدمة في لغة جافا سكريبت ولغات أخرى لتعريف الكائنات، وتُعرَّف المفاتيح في هذه الصيغة مثل رموز، أي يمكن الوصول للقيم في المثال السابق باستخدام الرموز first_name: و last_name: بدلًا من السلاسل النصية "first_name" و "last_name". الآن، بعد أن تعرفنا على العديد من أنواع البيانات في لغة روبي سنتعرف في الفقرة التالية كيف يتعامل روبي معها. لغة روبي ديناميكية الأنواع تمتاز لغة روبي بأنها ديناميكية الأنواع dynamic typing، أي لا يُعرّف نوع البيانات صراحةً عند إسناد قيمتها إلى متغير، بل يُحدّد نوع البيانات تلقائيًا بحسب القيمة التي خُزّنت فيه، مما يعني أن عملية فحص النوع ستجري خلال وقت التشغيل للبرنامج وليس في وقت الترجمة كما هو الحال في اللغات التي تكون ثابتة الأنواع static typing، وهناك عدة لغات شهيرة تستخدم نفس الطريقة مثل لغة بايثون ولغة جافا سكريبت. تتيح ديناميكية الأنواع إمكانية إسناد قيمة للمتغير t التالي مثلًا من أي نوع من أنواع البيانات المتاحة: t = 42 # t is an Integer t = "Sammy" # t is a String t = :sammy # t is a Symbol t = true # t is a boolean (true) t # t is nil كما تتيح هذه الميزة إمكانية تعديل القيمة المُخزنة ضمن المتغير نفسه وإسناد قيمة جديدة من نوع بيانات مختلف، ففي المثال التالي سنطلب من المستخدم إدخال قيمة عددية: print "Please enter the length of the room: " length = gets.chop وبما أن القيمة التي سيدخلها المستخدم من لوحة المفاتيح ستكون من نوع سلسلةً نصيةً دائمًا، فيجب تحويلها إلى عدد أولًا قبل إجراء العمليات الحسابية عليها، ففي اللغات ثابتة الأنواع static typing سنضطر لتعريف نوع البيانات ضمن المتغير قبل إسناد القيمة له، وبذلك علينا إنشاء متغير جديد بنوع بيانات مناسب لتخزين قيمة البيانات بعد تحويلها ولن نتمكن من إعادة استخدام نفس المتغير، ولكن في لغة روبي وبما أنها ديناميكية الأنواع فذلك ممكن على النحو التالي: # Convert the amount to a Float. length = length.to_f استخدمنا التابع to_f لتحويل قيمة السلسلة النصية إلى عدد عشري، كما يوجد التابع to_i لتحويل قيمة السلسلة النصية إلى عدد صحيح، ويمكن تحويل أغلب الكائنات في روبي إلى سلاسل نصية باستخدام التابعto_s: 42.to_s # "42" (42.5).to_s # "42.5" ["Sammy", "Shark"].to_s # "[\"Sammy\", \"Shark\"]" لا تعني ديناميكية الأنواع إمكانية تنفيذ العمليات بين أنواع البيانات المختلفة دون تحويلها إلى نفس النوع أولًا، فمثلًا سينتج عند جمع سلسلة نصية مع عدد صحيح: print 5 + "5" الخطأ التالي في الخرج: TypeError: String can't be coerced into Integer حتى لو نفذنا نفس العملية بعكس ترتيب القيم: print "5" + 5 سينتج خطأ أيضًا: TypeError: no implicit conversion of Integer into String لحل هذه المشكلة يجب تحويل السلسلة النصية إلى عدد صحيح أولًا ثم جمعها للحصول على النتيجة 10، أما إذا أردنا دمجهما معًا مثل سلاسل نصية للحصول على السلسلة النصية "55" فيجب تحويل العدد إلى سلسلة نصية قبل جمعه. من مزايا اللغات ديناميكية الأنواع المرونة في استخدام البيانات والتعامل مع المتغيرات، ولكن العيب الوحيد في ذلك هو عدم القدرة على تحديد نوع البيانات المخزن ضمن متغير ما قد جرى التعديل عليه، ولحل هذه المشكلة توفر روبي طرقًا لتحديد نوع بيانات أي قيمة مستخدمة سنتعرف عليها في الفقرة التالية. تحديد أنواع البيانات معظم القيم في لغة روبي هي كائنات، فالأعداد الصحيحة والعشرية والمصفوفات والرموز وجداول Hash كلها كائنات في روبي، ويحتوي كل منها على تابع يسمى class يعيد عند استدعائه نوع ذلك الكائن، وحتى القيم المنطقية true و false والقيمة nil كائنات أيضًا: 42.class # Integer (42.2).class # Float ["Sammy", "Shark"].class # Array true.class # TrueClass nil.class # NilClass كما يمكن استخدام تابع ?kind_of للتحقق من نوع البيانات على النحو التالي: 42.kind_of?(Integer) # true نستفيد من ذلك للتعرف على نوع البيانات المُخزنة ضمن متغير ما: # نوع القيمة المسندة واضح عند التعريف sharks = ["Hammerhead", "Tiger", "Great White"] ... # لكنه غير واضح في أماكن أخرى من البرنامج sharks.kind_of?(Hash) # false sharks.kind_of?(Array) # true يمكن استخدام هذه الطريقة أيضًا للتحقق من صحة البيانات القادمة من مصدر خارجي: if data.kind_of? String data = data.to_f end يوجد أيضًا التابع ?is_a والذي له نفس وظيفة التابع ?kind_of ولكنه أسهل بالقراءة: if data.is_a? String data = data.to_f end يساعد استخدام التوابع class و ?kind_of و ?is_a في التأكد من النوع الصحيح للبيانات الذي نتعامل معه، وهناك طرق أخرى سنتعلمها لاحقًا تغني عن الحاجة للتحقق من نوع البيانات يدويًا. الخاتمة تعرفنا في هذا المقال على العديد من أنواع البيانات التي توفرها روبي، إذ يمكّننا فهم أنواع البيانات تلك ووظائفها من التعامل معها وإجراء العمليات عليها لتطوير البرامج المختلفة، وهي من الأفكار الأساسية في لغة روبي والعديد من لغات البرمجة الأخرى. ترجمة -وبتصرف- للمقال Understanding Data Types in Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: التعليقات في لغة روبي Ruby دليلك الشامل إلى أنواع البيانات مدخل إلى البيانات وأنواعها: أنواع البيانات الأساسية السلاسل والمتغيرات في روبي
-
التعليقات هي أسطر تتجاهلها المصرّفات compilers والمفسرات interpreters في البرامج الحاسوبية، ويمكننا الاستفادة من التعليقات لتوضيح البرامج للمبرمجين الآخرين عبر تفسير الشيفرات البرمجية ووظيفة كل جزء من البرنامج، إضافةً إلى استخدام التعليقات لشرح لماذا اخترتنا حلًا معينًا مثلًا، أو حتى لتعطيل جزء من البرنامج غير مكتمل ومنعه من التنفيذ مؤقتًا حتى إكماله أو إصلاحه. قد تبقى بعض التعليقات في الشيفرات البرمجية إلى الأبد مثل تلك التي تشرح السياق ضمن البرنامج، أو قد تكون مؤقتة مثل الملاحظات التي نتركها لأنفسنا أثناء تطوير البرنامج. سنتعرف في هذا المقال على طريقة استخدام التعليقات في برامج روبي لترك ملاحظات أو استخدامها مثل أداة لإصلاح الأخطاء. صيغة التعليقات واستخدامها تبدأ التعليقات في لغة روبي بالعلامة (#) ويُعد السطر بعدها كله تعليق على النحو التالي: # سطر تعليق في روبي يُفضّل وضع مسافة فارغة بعد رمز التعليق لتوضيح التعليق رغم أن ذلك ليس ضروريًا، وعند تنفيذ البرنامج فلن نلاحظ أي تغيير يدل على وجود التعليقات ضمن الشيفرة البرمجية، إذ يتجاهلها مفسّر روبي تمامًا لأنها موجودة لنقرأها نحن فقط وليس للحواسيب. يمكن الكتابة بأي لغة نريدها ضمن التعليقات مثل اللغة العربية أو الإنجليزية، ويُفضل اختيار اللغة المناسبة التي يفهمها كل من يعمل على المشروع، ويمكن استخدام التعليقات لتوضيح تفاصيل إضافية حول ما يحدث في كل جزء من الشيفرة البرمجية مثل الشيفرة التالية التي عملنا عليها في مقال سابق: # عرض رسالة للمستخدم puts "Please enter your name." # (حفظ القيمة المدخلة وإزالة آخر حرف (محرف السطر الجديد name = gets.chop # طباعة النتيجة على الشاشة puts "Hi, #{name}! I'm Ruby!" نلاحظ أن تلك التعليقات توضح وظيفة كل جزء من البرنامج وكيف يعمل. يمر البرنامج التالي على عناصر المصفوفة ويعرض محتوياتها مثل قائمة بصيغة HTML، ويمكن ملاحظة كيف أن التعليقات توضح ما تفعله الشيفرة أكثر: sharks = ['hammerhead', 'great white', 'dogfish', 'frilled', 'bullhead', 'requiem'] # مع إضافة مسافات في بدايتها وأسطر جديدة HTML تحويل كل عنصر في المصفوفة إلى عنصر listitems = sharks.map{ |shark| " <li>#{shark}</li>\n"} # المفتوحة، ثم طباعة مصفوفة عناصر القائمة <ul> طباعة print "<ul>\n#{listitems.join}</ul>" استخدمنا في هذا المثال توابعًا قد تكون جديدة، مثل map و join. نلاحظ هنا فائدة التعليقات، إذ توضح عمل البرنامج والخرج الناتج. لنختبر الآن البرنامج بحفظه في ملف بالاسم "sharks.rb"، ثم تنفيذه. $ ruby sharks.rb نلاحظ الخرج التالي: <ul> <li>hammerhead</li> <li>great white</li> <li>dogfish</li> <li>frilled</li> <li>bullhead</li> <li>requiem</li> </ul> نلاحظ عدم وجود التعليقات التي أضفناها، إذ يتجاهلها المفسّر تمامًا وكأنها غير موجودة؛ فدورها يقتصر على توضيح البرنامج وتساعدنا في توقع شكل الخرج عند التنفيذ، وهذا أفضل استخدام للتعليقات فهي ستساعد أي شخص يقرأها والذي قد يكون جديدًا على اللغة. يُفضل محاذاة التعليقات بنفس مستوى الإزاحة الخاص بالشيفرة التي تتعلق بها وتشرحها، فمثلًا عند شرح صنف ما تعريفه لا يحتوي على إزاحة نكتب التعليق الخاص بلا إزاحة أيضًا، والتعليقات الخاصة بالتوابع ضمن ذلك الصنف يجب أن تبدأ بنفس مستوى الإزاحة لكل تابع. لنلاحظ المثال التالي وهو للعبة Magic 8-Ball تعطي لنا إجابة عشوائية على السؤال الذي نطرحه، ونلاحظ أن التعليقات محاذية مع الشيفرة تشرحا في كل قسم: # صنف اللعبة class Eightball # إعداد الخيارات المتاحة def initialize @choices = ["Yes", "No", "All signs point to yes", "Ask again later", "Don't bet on it"] end # اختيار خيار عشوائي من الخيارات المتاحة def shake @choices.sample end end def play puts "Ask your question." # نتجاهل الدخل بما أننا لا نريده gets # نُنشئ كائن جديد من صنف اللعبة ونستخدمه للإجابة game = Game.new answer = game.shake puts answer # نسأل المستخدم في حال أراد إعادة اللعبة وتقييم الجواب puts "Want to try again? Press 'y' to continue or any other key to quit." answer = gets.chop if answer == 'y' play else exit end end # بدء اللعبة لأول مرة play دور التعليقات الأساسي هو مساعدة المبرمجين، سواء كان المبرمج هو من كتب الشيفرة البرمجية، أو شخص آخر يستخدمها أو يعمل عليها، لذا يجب أن نعامل التعليقات تمامًا كما نعامل الشيفرة البرمجية من ناحية الاهتمام بها وتحديثها وتوضيحها وتنسيقها، إذ يضر وجود تعليق مبهم أو يشرح على نحوٍ خاطئ أكثر من عدم وجود ذلك التعليق من الأساس. في بداية رحلتك كمبرمج، ستكتب الكثير من التعليقات التي تساعدك في فهم ما تفعله، ولكن مع اكتساب المزيد من الخبرة سيتغير محتوى تعليقاتك لشرح سبب وجود الشيفرة البرمجية التي تحاول شرحها بدلًا من شرح الشيفرة نفسا أو طريقة عملها، وبذلك ستكوّن فكرةً عن دور الشيفرة وما تفعله بنظرة بسيطة عليها، إلا في حال كانت الشيفرة معقدة بعض الشيء، فمثلًا التعليق التالي يشرح الشيفرة نفسها وهو ليس مفيدًا في حال كنت تعرف لغة روبي: # على الشاشة"Hello World" يطبع العبارة print "Hello World" فهو يكرر شرح ما تفعله الشيفرة، ورغم أن وجوده لن يغير أو يؤثر في عمل البرنامج إلا أنه حشو زائد عن الحاجة ولا يضيف أي قيمة جديدة للمبرمج قارئ الشيفرة، ولكن قد نحتاج أحيانًا للاستفاضة عند كتابة تعليقات وإطالة الشرح على عدة أسطر ولن يكفي سطر واحد، وهذا ما توفره التعليقات الكتلية التي سنتعرف عليها في الفقرة التالية. التعليقات الكتلية يمكننا استخدام التعليقات الكتلية Block Comments لشرح الشيفرة المعقدة أو الشيفرة التي لا نتوقع أن يكون القارئ على دراية بها، إذ يشرح هذا النوع من التعليقات أجزاء أو كل الشيفرة التي تليها، وتكون بنفس محاذاتها، وكما هو الحال في التعليقات العادية، يبدأ كل سطر منها بالمحرف (#) يليه مسافة اختيارية للتوضيح، وتُكتب تلك الأسطر خلف بعضها، ففي المثال التالي تعليق كتلي من الشيفرة المصدرية لإطار العمل Sinatra، ونلاحظ كيف يشرح السياق للمطورين الآخرين وطريقة عمل هذه الشيفرة: ... # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however, # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question. # This middleware will detect an extended body object and will make sure it reaches the # handler directly. We do this here, so our middleware and middleware set up by the app will # still be able to run. class ExtendedRack < Struct.new(:app) def call(env) result, callback = app.call(env), env['async.callback'] return result unless callback and async?(*result) after_response { callback.call result } setup_close(env, *result) throw :async end ... ومع أن التعليقات الكتلية مفيدة لشرح الشيفرة بتفصيل أكثر، إلا أنه يفضل تجنب كثرة استخدامها، فقد تكون مجرد تكرار وحشو إضافي زائد، وبدلًا منها علينا أن نثق بخبرة المبرمجين الآخرين لفهم شيفرات روبي بأنفسهم، إلا إذا كانت تٌكتب تلك التعليقات لجمهور محدد، فيجب أن يقتصر دور التعليقات في توضيح السياق والسبب في وجود الشيفرة وليس تكرار شرح عملها. توفر روبي أيضًا صيغةً أخرى لكتابة التعليقات الكتلية على عدة أسطر، ولكنها نادرة الاستخدام: =begin تعليق على عدة أسطر يمكننا استخدام مثل هذه التعليقات للكتابة بأسطر عدة =end يبدأ التعليق بالكلمة begin= وينتهي بالكلمة end=، ويجب أن تكون تلك الكلمات في بداية السطر تمامًا دون مسافات قبلها، ولهذا السبب نادرًا ما تُستخدم، فلا يمكن محاذاتها سوى مع بداية السطر. توفّر روبي أيضًا نوعًا آخر وهو التعليقات السطرية والتي يمكن كتابتها مع الأسطر البرمجية سنتعرف عليها في الفقرة التالية. التعليقات السطرية يمكن كتابة هذا النوع من التعليقات على نفس سطر الشيفرة البرمجية وبعدها تمامًا، إذ تبدأ أيضًا بالمحرف (#)، ثم مسافة فارغة اختيارية للتوضيح على النحو التالي: [تعليق سطري عن الشيفرة # [الشيفرة البرمجية يجب استخدام التعليقات السطرية بحذر، لكنها مفيدة لشرح بعض الأجزاء المعقدة، أو غير الواضحة من الشيفرة، أو في حال احتمال نسيان وظيفة سطر معين في المستقبل، فمثلًا إذا لم يكن جميع من يعمل على المشروع بدراية كافية في مجال الرياضيات وتطبيقها في برامج روبي، فيمكننا شرح بعض الأجزاء والغاية منها، مثل الشرح التالي عن إنشاء عدد معقد ولكن بتعليق سطري: a=Complex(4,3) # (4+3i)إنشاء العدد المعقد والتعليق التالي الذي يشرح سبب اختيارنا لحل ما: pi = 3.14159 # حددنا عدد الخانات العشرية عن قصد في هذا البرنامج يفضل استخدام التعليقات السطرية عند الضرورة فقط لشرح سطر برمجي أو توفير إرشاد معين لمن يقرأ البرنامج. استخدام التعليقات أثناء الاختبار تعرّفنا في الفقرات السابقة على توظيف التعليقات لإضافة توثيق وشرح للشيفرات البرمجية، إذ يمكن استخدام ميزة التعليقات بوضع الرمز (#) في بداية أي سطر من الشيفرة لا نريد تنفيذه مؤقتًا، وذلك مفيدٌ أثناء تطوير واختبار أو تصحيح الأخطاء في البرنامج، فقد نواجه في بعض الأحيان مشاكلًا بعد إضافة أسطر جديدة من الشيفرة، ويمكننا لحصر سبب المشكلة تعليق بعض الأسطر المضافة حديثًا لمعرفة سبب الخطأ، إذ يمكننا مثلًا في مثال اللعبة السابق منع إعادة تشغيل اللعبة في حال كنا نختبر صحة عملية تقييم. يمكننا تعليق السطر من الشيفرة الذي يعيد تشغيل اللعبة مرةً أخرى لتعطيل تلك الميزة على النحو التالي: ... # Prompt to restart the game and evaluate the answer. puts "Want to try again? Press 'y' to continue or any other key to quit." answer = gets.chop if answer == 'y' # play else exit end end ... يمكن استخدام تعليق الشيفرات أيضًا أثناء تجربتنا لحلول بديلة ومعاينتها، فقد ترغب مثلًا بتجربة الطرق المختلفة الممكنة للتعامل مع المصفوفات في روبي، فنعلِّق الطريقة الحالية ونكتب بعدها طريقة جديدة ما لاختبارها وتحديد الأفضل منها: sharks = ["Tiger", "Great White", "Hammerhead"] # for shark in sharks do # puts shark # end sharks.each do |shark| puts shark end كما لاحظنا يفيد تعليق الشيفرات البرمجية في عملية تحديد الأخطاء وأثناء التطوير وتجربة حلول مختلفة، وذلك بتعليق أجزاء من البرنامج ومنعها من التنفيذ وتشغيله بصورةٍ نظامية ومعاينة الفرق. الخاتمة تعرفنا في هذا المقال على التعليقات في لغة روبي وطرق استخدامها والاستفادة منها لتوضيح قراءة الشيفرات البرمجية وفهمها من قبل المبرمجين العاملين عليها وحتى بمثابة ملاحظات مستقبلية للمبرمج الذي يكتب الشيفرة نفسه، مما يسهل العمل على المشروع أو البرنامج ويفيد كل من يعمل عليه، كما أنها تساعدك في فهم الشيفرة البرمجية عند الرجوع إليها بعد مدة طويلة ونسيان بعض التفاصيل والحلول المستخدمة. ترجمة -وبتصرف- للمقال How To Use Comments in Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: استكشاف لغة روبي باستخدام البرنامج التفاعلي IRB دليل تعلم البرمجة قواعد البرمجة ببساطة للمبتدئين دليل تنسيق شيفرات روبي Ruby
-
يوفر برنامج روبي التفاعلي Interactive Ruby -أو اختصارًا IRB طريقةً سريعةً لاستكشاف لغة برمجة روبي وتجربة الشيفرات البرمجية دون الحاجة لإنشاء ملفات جديدة وتنفيذها، إذ يُعد برنامج IRB حلقة قراءة-تقييم-طباعة Read-Eval-Print Loop -أو ما يعرف اختصارًا REPL- وهي أداة توفرها العديد من لغات البرمجة الحديثة لتوفير طريقة سهلة للعب واستكشاف لغة البرمجة، ولاستخدامها نُنفذ الأمر irb ثم نكتب مباشرةً كود روبي ليترجم IRB الشيفرة المدخلة ويقيّمها ويعرض النتيجة. يمكن ضمن IRB الوصول لجميع المزايا ضمن روبي إضافةً لأي مكتبات أو gems مثبتة على جهازك، ويمكنك أيضًا إعداد IRB لحفظ سجل الأوامر المدخلة خلال الجلسة، وحتى أنه يوفر ميزة الإكمال التلقائي للشيفرة أثناء إدخالها. سنتعرف في هذا المقال على طريقة استخدام IRB لتنفيذ بعض الشيفرات ومعاينة نتيجة تنفيذها واستدعاء المكتبات الخارجية وحتى تخصيص جلسة IRB. تشغيل وإيقاف IRB يكفي تثبيت لغة روبي على الجهاز ليكون IRB مثبتًا وجاهزًا للاستخدام، ويمكننا تشغيله مباشرةً بتنفيذ الأمر irb من سطر الأوامر على النحو التالي: $ irb ليظهر لنا نافذة IRB: irb(main):001:0> يدل السياق الافتراضي الأعلى في برنامج روبي على أننا ضمن برنامج IRB وأن أي شيفرات سننفذها ستُنفذ ضمن السياق main، كما أنه سيعرض رقم السطر الحالي، وفي حال تثبيت روبي باستخدام RVM، فقد يكون شكل النافذة مختلفًا قليلًا، إذ سيعرض رقم الإصدار الحالي للغة روبي بدلًا من ذلك: 2.4.0 :001 > وللحصول على نفس نافذة الـ IRB ضمن الأمثلة في هذا المقال يمكن تشغيل IRB باستخدام الأمر التالي: irb --prompt inf-ruby يمكن ضمن IRB إدخال التعليمات بصيغة لغة برمجة روبي مباشرةً لنختبر ذلك بإضافة عددين معًا: irb(main):001:0> 2 + 2 نضغط على مفتاح "Enter" ليعرض IRB نتيجة تنفيذ تلك التعليمة: => 4 يشير الرمز <= في بداية السطر بأن هذه هي قيمة هي نتيجة تنفيذ عبارة روبي البرمجية السابقة، وللخروج من IRB ننفذ التعليمة exit أو نضغط على المفتاحين "CTRL+D" لنعود بذلك إلى واجهة سطر الأوامر. سنتعمق في الفقرة التالية في IRB وسنستخدمه لاستكشاف الشيفرات البرمجية. تنفيذ الشيفرات البرمجية ضمن جلسة IRB يُعد IRB وسيلةً رائعةً لتجربة الشيفرات البرمجية واختبارها سريعًا لمعرفة ما إذا كانت ستحل المشكلة المطلوبة، فمعظم الأوامر في روبي تعيد قيمةً ما عند تنفيذها وهو ما يظهر عند تنفيذ أي تعليمة في IRB، إذ سيطبع تلك القيمة على الشاشة. لنختبر ذلك بتنفيذ الأمر التالي ضمن جلسة IRB جديدة: irb(main):001:0> puts "Hello World" عند الضغط على "Enter" سنرى نتيجتين من IRB: Hello World => nil السطر الأول هو ناتج عن تنفيذ التابع puts، الذي يطبع النص الممرر له وبعده محرف لسطر جديد إلى وحدة الخرج القياسي والذي هو الشاشة عادةً. يعيد التابع puts نفسه قيمةً عند تنفيذه وذلك لأن كل تابع في روبي يجب أن يعيد قيمةً ما، والقيمة التي يعيدها التابع puts هي nil، وهذا تمامًا ما يعرضه IRB في السطر الثاني، وفي كل مرة ننفذ فيها تعليمةً برمجيةً، سيتغير رقم السطر الحالي إلى رقم سطر جديد: irb(main):001:0> puts "Hello World" Hello World => nil irb(main):002:0> يفيد عرض رقم السطر الحالي خلال عملية تصحيح الأخطاء عند تنفيذ التعليمات المعقدة، فعند حدوث أخطاء، ستشير رسالة الخطأ إلى أرقام الأسطر التي نتج عنها الخطأ، ويمكن استخدام IRB لتعريف المتغيرات وإسناد القيم لها بنفس الصيغة المستخدمة ضمن برامج روبي، فمثلًا يمكننا تنفيذ الأمر التالي ضمن جلسة IRB والضغط على "Enter": irb(main):002:0> birth_year = 1868 لنرى القيمة التي تعيدها تلك التعليمة البرمجية: => 1868 يحتوي المتغير birth_year على تلك القيمة. بما أن معظم الأوامر في روبي تُرجع قيمًا، فسيعرض IRB لنا القيمة التي تعيدها تعليمة الإسناد هنا أيضًا. لنعرف متغيرًا آخرًا بالاسم death_year على النحو التالي: irb(main):003:0> death_year = 1921 ثم نعرف المتغير age_at_death عن طريق طرح قيمة birth_year من قيمة المتغير death_year: irb(main):004:0> age_at_death = death_year - birth_year يسند IRB القيمة للمتغير الجديد ويعرض النتيجة: => 53 نلاحظ عدم حاجتنا للاستعادة بالتوابع puts أو print ضمن جلسة IRB، إذ تُطبع تلقائيًا القيم التي تعيدها التعليمات البرمجية المُنفذة. يدعم IRB كتابة الشيفرات البرمجية التي تمتد على عدة أسطر، إذ يُنفذ تلك الشيفرات البرمجية بعد اكتمال صياغتها بصورةٍ صحيحة، ففي الشيفرة البرمجية التالية نستخدم مصفوفة من أسماء الأسماك ونستدعي منها التابع select لإرجاع أسماء الأسماك التي تحتوي على الحرف "a" في اسمها، ولكتابة هذه الشفرة على عدة أسطر ضمن جلسة IRB نضغط مفتاح "Enter" بعد كل سطر منها: ["Tiger", "Great White", "Angel"].select do |shark| shark.include?("a") end سينفذ IRB الشيفرة البرمجية فقط عند اكتمال صحة صياغتها، ونلاحظ ظهور رمز النجمة * في بداية الأسطر دلالةً على عدم تنفيذ IRB للشيفرات السابقة بعد، وتتغيير القيمة صفر في نهاية اسم السطر إلى واحد للدلالة على الدخول في سياق جديد مختلف: irb(main):005:0> ["Tiger", "Great White", "Angel"].select do |shark| irb(main):006:1* shark.include?("a") irb(main):007:1> end بما أن السطر الأول يحتوي على الكلمة المفتاحية do، فإن IRB لا ينفذ أي تعليمة لاحقة حتى يصل إلى الكلمة المفتاحية end، ومن ثم سيعرض النتيجة: => ["Great White"] يمكن باستخدام IRB اختبار الشيفرات البرمجية الصغيرة لاستكشاف طريقة عملها قبل اعتمادها ضمن البرامج التي نعمل على تطويرها، كما يمكن استخدام IRB لاستدعاء المكتبات الخارجية والتعامل معها وهو ما سنتعرف عليه في الفقرة التالية. استخدام المكتبات والأمر Gem يمكنك استيراد المكتبات إلى جلسة IRB باستخدام تعليمة require تمامًا كما نفعل في برنامج روبي، ويمكن أن تكون أحد المكتبات القياسية ضمن مكتبة روبي، أو مكتبات خاصة بنا، أو مكتبات Gems وهي مكتبات روبي المنشورة ضمن المستودع Rubygems.org، والتي يمكننا تثبيتها باستخدام أمر gem؛ إذ تتضمن مكتبة روبي القياسية وحدات تمكننا من إرسال الطلبات عبر الويب مثلًا واستقبال نتائجها، ويمكن استخدام تلك المكتبة من داخل جلسة IRB تمامًا كما نفعل في برنامج روبي؛ إذ تُستخدم التعليمة require لاستيراد المكتبة Net/HTTP من مكتبة روبي القياسية بإدخال السطر التالي ضمن جلسة IRB والضغط على ENTER: require 'net/http' يعيد تنفيذ هذا السطر في IRB القيمة true، مما يدل نجاح تحميل المكتبة، ويمكننا الآن استخدامها لكتابة الشيفرة التالية لإرسال طلب إلى موقع مثلًا icanhazip.com لمعرفة عنوان IP الخارجي الخاص بنا: uri = URI.parse("http://icanhazip.com") response = Net::HTTP.get_response uri response.body بعد إدخال كل سطر من الأسطر السابقة سيعرض IRB القيمة التي تعيدها كل تعليمة منها، ونستفيد من ذلك لتصحيح الأخطاء في كل خطوة في حال حدوثها: irb(main):010:0> uri = URI.parse("http://icanhazip.com") => #<URI::HTTP http://icanhazip.com> irb(main):011:0> response = Net::HTTP.get_response uri => #<Net::HTTPOK 200 OK readbody=true> irb(main):012:0> response.body => 203.0.113.52\n فمثلًا في حال لم يعثر على المكتبة المطلوبة، ستكون القيمة التي تعيدها التعليمة مختلفة. لنختبر ذلك باستيراد مكتبة HTTParty، والتي توفر طريقة أسهل للتعامل مع طلبات HTTP: require 'httparty' نلاحظ ظهور هذه الرسالة: LoadError: cannot load such file -- httparty والتي تخبرنا بأن المكتبة التي نريدها غير متاحة، إذ أن مكتبة HTTParty منشورةٌ على أنها مكتبة gem، لذلك نحتاج لتثبيتها قبل الاستخدام، لذا نخرج من جلسة IRB باستخدام المفتاحين "CTRL+D"، أو نكتب exit للعودة إلى سطر الأوامر، ثم ننفذ الأمر gem لتثبيت مكتبة httparty: $ gem install httparty الآن، نعود لجلسة irb مرةً أخرى: $ irb ونحاول تحميل وحدة المكتبة مرةً أخرى على النحو التالي: require 'httparty` نلاحظ أن قيمة تنفيذ تلك التعليمة هذه المرة هي true، مما يدل على نجاح تحميل المكتبة. لنستخدمها الآن كما يلي: response = HTTParty.get("http://icanhazip.com") response.body سنرى نتيجة قيمة رد الطلب المرسل على النحو التالي: => 203.0.113.52\n بعد أن تعرفنا على طريقة استدعاء واستخدام أي نوع من المكتبات ضمن IRB، لنتعرف على طريقة استخدامه لاستكشاف واختبار شيفرات روبي البرمجية في الفقرة التالية. تحميل الشيفرات البرمجية ضمن IRB يمكن استخدام الخيار r- عند إنشاء جلسة IRB جديدة لتحديد المكتبات أو Gems التي نريد تحميلها مباشرةً عند بدء جلسة IRB، فمثلًا يمكن تنفيذ الأمر irb -r httparty، الذي سيبدأ جلسة IRB جديدة مع تحميل مكتبة httparty مباشرةً، ما يعني عدم الحاجة لتنفيذ التعليمة require httparty ضمن الجلسة لاستيراد المكتبة، ويمكن استخدام هذا الخيار أيضًا لتحميل شيفرة برمجية خاصة بنا ضمن الجلسة الجديدة مما يفيد عند الحاجة لاستكشاف تلك الشيفرات أو اختبارها. ننهي جلسة IRB الحالية بكتابة exit أو بالضغط على المفتاحين "CTRL+D"، ثم ننشئ ملف روبي جديد بالاسم "ip_grabber.rb"، والذي سنعرف ضمنه كائنًا IPGrabber مع تابع get الذي يعيد عنوان IP الخارجي للجهاز. سنستعين بالمكتبة HTTParty لإرسال طلب HTTP وجلب قيمة العنوان من الموقع icanhazip.com، إذ يمكننا استخدام الكائن IPGrabber ضمن برامجنا الأخرى لعزل الشيفرة الخاصة بالبرنامج عن أي تغييرات خارجية، وسنتمكن من استخدام هذا الكائن حتى بعد تبديل المكتبة التي يستخدمها لإرسال طلبات HTTP أو تغيير الموقع الذي يستخدمه لجلب عنوان IP دون الحاجة إلى تعديل أي شيفرة ضمن البرامج الأخرى. نبدأ بإضافة الشيفرة التالية إلى الملف لتعريف الصنف الجديد: require 'httparty' class IPGrabber def initialize() @url = "http://icanhazip.com" end def get response = HTTParty.get(@url) response.body.chomp # remove the \n if it exists end end نحفظ الملف ونبدأ جلسة IRB ونحمل ذلك الملف ضمنها، وبما أنه ملف محلي وليس مكتبة خارجية أو مدمجة فيجب تحديد مسار هذا الملف عند تحميله ولا حاجة لكتابة اللاحقة rb. في نهاية اسم الملف: $ irb -r ./ip_grabber يمكننا الآن -بعد انتهاء تحميل جلسة IRB الجديدة- استخدام هذا الكائن مباشرةً في الجلسة على النحو التالي: ip = IPGrabber.new ip.get نلاحظ ظهور النتيجة وهي العنوان: => 203.0.113.52 تمكّننا خاصية تحميل الشيفرات الخاصة بنا ضمن جلسة IRB من فحصها أو استدعائها للعمل بها واختبارها قبل دمجها في البرامج التي نعمل على تطويرها. الآن، بعد أن تعرفنا على طريقة تحميل والعمل مع الشيفرات في جلسة IRB، سنتعرف على طريقة تخصيص جلسة IRB وإعدادها لتناسب حاجتنا. تخصيص جلسة IRB يمكننا عبر إنشاء ملف الإعدادات بالاسم "irbrc." تخصيص جلسة IRB، مثل إضافة ميزة دعم الإكمال التلقائي والمسافات وتحديد سجل تاريخ الأوامر. لنبدأ بإنشاء هذا الملف ضمن مجلد المستخدم: $ nano ~/.irbrc نبدأ بتفعيل ميزة الإكمال التلقائي في IRB، إذ يتيح لنا ذلك ميزة استخدام المفتاح "TAB" ضمن الجلسة لإكمال أسماء الكائنات والمتغيرات وأسماء التوابع: require 'irb/completion' نضيف بعدها تفعيل ميزة حفظ تاريخ الأوامر المدخلة في ملف خارجي: IRB.conf[:SAVE_HISTORY] = 1000 بذلك ستُسجل آخر 1000 عبارة برمجية ندخلها ضمن ملف بالاسم "irb_history." في مجلد المستخدم. إضافةً لذلك، عند فتح جلسة IRB جديدة، سيُحمل تاريخ هذه التعليمات تلقائيًا، ويمكننا استخدام مفاتيح الأسهم للأعلى أو الأسفل للتنقل بين هذه التعليمات، أو استخدام CTRL+R للبحث ضمنها تمامًا كما نفعل في صدفة باش bash، ويمكننا أيضًا تحديد ملف لحفظ تاريخ التعليمات مختلف بإضافة السطر التالي لملف الإعدادات: IRB.conf[:HISTORY_FILE] = '~/your_history_filename' نضيف السطر التالي لملف الإعدادات لتفعيل ميزة إدراج المسافات تلقائيًا في بداية الأسطر، وهي ميزة مفيدة عند كتابة الأصناف والتوابع والكتل البرمجية blocks: IRB.conf[:AUTO_INDENT] = true يمكن لملف الإعدادات أن يحتوي شيفرات برمجية بلغة روبي، ما يعني إمكانية تعريف التوابع المساعدة أو استخدام require لتحميل المكتبات الإضافية، فمثلًا يمكننا تعريف التابع المساعد history إلى جلسة IRB لعرض التاريخ بإضافته على النحو ضمن ملف الإعدادات "irbrc.": def history history_array = Readline::HISTORY.to_a print history_array.join("\n") end يمكننا بعد إنشاء جلسة IRB جديدة كتابة history لعرض تاريخ IRB، وقد يحتوي التاريخ على عدد كبير من السجلات لذا يمكننا التعديل على الأمر history ليأخذ عددًا اختياريًا يعبر عن عدد الأسطر التي نود عرضها، لذا نستبدل الشيفرة الخاصة به لتأخذ معاملًا اختياريًا يسمى count وتستخدمه لتحديد عدد السجلات التي يعرضها: # history command def history(count = 0) # Get history into an array history_array = Readline::HISTORY.to_a # if count is > 0 we'll use it. # otherwise set it to 0 count = count > 0 ? count : 0 if count > 0 from = history_array.length - count history_array = history_array[from..-1] end print history_array.join("\n") end نحفظ الملف وننشئ جلسة IRB جديدة ثم نكتب history 2 لنرى فقط آخر سطرين من سجل التاريخ الموجود. يجب التذكر أنه وبالرغم من إمكانية استخدام ملف الإعدادات "irbrc." لتحميل المكتبات التي نستخدمها بصورةٍ متكررة ضمن جلسات IRB إلا أن كل مكتبة منها ستزيد وقت تحميل كل جلسة جديدة، ولتجنب ذلك يُفضّل تحميل تلك المكتبات يدويًا باستخدام require عند الحاجة إليها ضمن كل جلسة على حدى. الخاتمة يوفر لنا IRB مكانًا لاستكشاف وتجربة شيفرات روبي البرمجية بسهولة، وهو وسيلة رائعة لاستخدامها لتطوير أجزاء من البرنامج الذي نعمل عليه قبل اعتماده وحفظه ضمن ملف. الآن، بعد أن تعرفنا على IRB، يمكنك استخدامه لاستكشاف أنواع البيانات المختلفة المتاحة في روبي أو استكشاف أي مزايا أخرى واستخدام وتجربة المكتبات والتعرف عليها. ترجمة -وبتصرف- للمقال How To Use IRB to Explore Ruby لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: كتابة أول برنامج بلغة روبي كل ما تحتاج إليه لتعلم البرمجة بـ Ruby الدليل السريع إلى لغة البرمجة روبي Ruby
-
سنكتب معًا في هذا المقال أول برنامج بلغة روبي، وبدلًا من كتابة البرنامج التقليدي "Hello, World" سنكتب برنامجًا يطلب من المستخدم إدخال اسمه، ثم يستخدم هذا الاسم في طباعة جملة تحية. الخرج التالي هو مثال عن البرنامج الذي سنطوره في هذا المقال: Please enter your name. Hassan Hello, Hassan! I'm Ruby! سنحتاج بدايةً لبيئة تطوير روبي مثبتة، ويمكنك الاطلاع على المقالات السابقة في سلسلة البرمجة بلغة روبي للتعرف على طريقة إعداد بيئة تطوير روبي. كتابة برنامج !Hello, World سنكتب بدايًة البرنامج التقليدي "!Hello, World"، لذلك نفتح أي محرر نصوص عبر سطر الأوامر مثل نانو nano ونُنشئ ملفًا جديدًا على النحو التالي: $ nano hello.rb بعد فتح الملف نكتب البرنامج التالي: puts "Hello, World!" عند تنفيذ البرنامج سيطبع التابع puts المتاح في روبي سلسلة الحروف المُمررة له بين علامتي التنصيص على الشاشة، وتدعى هذه السلسلة من الحروف بالسلاسل النصية string. توفر لغة روبي عدة توابع جاهزة، مثل التابع puts، إذ يمكن استخدام تلك التوابع مباشرةً في البرنامج كما يمكننا تعريف توابع جديدة خاصة بنا. الآن، نحفظ التغييرات ونخرج من المحرر بالضغط على المفتاحين "Ctrl+X" ولتأكيد حفظ الملف نضغط على المفتاح "y" ثم "Enter". سنشغّل في الفقرة التالية البرنامج لتجربته ونرى النتيجة. تشغيل برنامج روبي بعد كتابتنا لبرنامج "!Hello, World" في الفقرة السابقة، ننفذ ذلك البرنامج باستخدام الأمر التالي: $ ruby hello.rb سيظهر النص "!Hello, World" على الشاشة، إذ يقرأ مترجم لغة روبي بتنفيذ الأمر ruby الملف المحدد ويعاين محتواه ويترجمه، ثم يبدأ بتنفيذ الأمر "!puts "Hello, World باستدعاء الدالة puts، ويمرر لها النص "!Hello, World" مثل معامل لتلك الدالة، إذ تدعى القيم التي تمرر لأي دالة بالوسيط argument، ولاحظ أن علامات الاقتباس التي تحيط بالنص "!Hello, World" لا تطبع على الشاشة فهي تستخدم فقط لإخبار روبي بأن تلك القيمة هي سلسلة نصية بتحديد بداية ونهاية السلسلة بعلامات الاقتباس تلك. الآن، بعد نجاح تنفيذ البرنامج سنطوّره في الفقرة التالية ليصبح تفاعليًا. طلب الدخل من المستخدم كتبنا في الخطوة السابقة برنامجًا يطبع نصًا ثابتًا على الشاشة، دون أن يتفاعل البرنامج مع المستخدم، وسنطلب في هذه الفقرة من المستخدم أن يدخل قيمةً، وسنتعرف على طريقة استخدامها في البرنامج، ونبدأ بكتابة برنامج جديد بفتح محرر النصوص نانو وإنشاء ملف جديد كما فعلنا سابقًا: $ nano greeting.rb نضيف السطر التالي لنطلب من المستخدم إدخال اسمه: puts "Please enter your name." استخدمنا التابع puts مرةً أخرى لطباعة نص السؤال على الشاشة، وسنضيف السطر التالي لاستقبال القيمة التي سيدخلها المستخدم: puts "Please enter your name." name = gets يخبر التابع gets البرنامج بانتظار دخل المستخدم من لوحة المفاتيح، إذ سيتوقف عندها البرنامج عن العمل لحين انتهاء المستخدم من إدخال أي نص يريده، وسيتابع البرنامج عمله بعد ضغط المستخدم على مفتاح "Enter" من لوحة المفاتيح، لتُحوّل كل تلك الضغطات على لوحة المفاتيح بما فيها المفتاح "Enter" إلى سلسلة من المحارف. بما أننا سنستخدم سلسلة المحارف تلك ضمن خرج البرنامج، سنحفظها ضمن متغير جديد بالاسم name باستخدام معامل الإسناد = ليخزن بذلك روبي تلك السلسلة في ذاكرة الحاسوب لحين انتهاء تنفيذ البرنامج، والآن نضيف السطر التالي لطباعة النتيجة النهائية: puts "Please enter your name." name = gets puts "Hi, #{name}! I'm Ruby!" نلاحظ استخدام التابع puts لطباعة سلسلة نصية مرةً أخرى، ولكن هذه المرة مررنا له سلسلةً نصيةً تحتوي على قيمة المتغير name ضمنها باستخدام ميزة في روبي لإدخال قيمة متغير ضمن سلسة نصية مباشرةً تدعى استيفاء السلسلة النصية string interpolation، إذ ستستبدل كلمة {name}# بالقيمة المحفوظة ضمن المتغير name، والتي يجب أن تكون اسم المستخدم الذي أدخله. الآن، نحفظ الملف ونخرج من محرر نانو بالضغط على مفتاحي "CTRL+X"، ثم تأكيد الحفظ بالضغط على "y" ثم مفتاح "Enter"، ولنشغل البرنامج ونختبره باستخدام الأمر التالي: $ ruby greeting.rb سيطلب البرنامج من المستخدم إدخال اسمه بعد الإدخال نضغط "Enter" ونلاحظ النتيجة: Please enter your name. Hassan Hi, Hassan ! I'm Ruby! بدلًا من طباعة !Hi, Hassan! I'm Ruby، طُبع فراغًا بعد الاسم المستخدم، والسبب في ذلك أن البرنامج استقبل جميع الضغطات على لوحة المفاتيح بما فيها المفتاح "Enter" في النهاية لإكمال الدخل، مما يؤدي أن السلسلة النصية ستنتهي بمحرف خاص يدل على طباعة سطر جديد، فالنتيجة التي يعرضها البرنامج هي فعلًا ما قد أُدخل من قبل المستخدم بما في ذلك محرف السطر الجديد. لحل تلك المشكلة نفتح ملف البرنامج "greeting.rb" ضمن محرر النصوص: $ nano greeting.rb ونبحث عن السطر التالي: name = gets نعدله ليصبح: name = gets.chop بالاستفادة من استدعاء التابع chop في روبي على السلسلة النصية المدخلة من قبل المستخدم باستخدام gets، سيُزال آخر محرف من السلسلة النصية وفي حالتنا هو محرف السطر الجديد الناتج عند الضغط على المفتاح "Enter". الآن، نحفظ الملف ونخرج من نانو بالضغط على CTRL+X ثم على المفتاح "y" لتأكيد حفظ الملف ونشغل البرنامج مرةً أخرى: $ ruby greeting.rb نلاحظ هذه المرة بعد إدخال الاسم والضغط على "Enter" طباعة عبارة التحية على سطر واحد كما نريد: Please enter your name. Sammy Hi, Sammy! I'm Ruby! وبذلك نكون قد طورنا برنامجًا بلغة روبي يقرأ دخلًا من المستخدم ويطبعه على الشاشة. الخاتمة تعرفنا في هذا المقال طريقة طلب الدخل من المستخدم في روبي ومعالجة ذلك الدخل لعرض النتيجة، ويمكن تطوير البرنامج أكثر، إذ يمكن مثلًا الطلب من المستخدم لونه المفضل وجعل البرنامج يطبع جملة "لونك المفضل هو الأحمر". ترجمة -وبتصرف- للمقال How To Write Your First Ruby Program لصاحبه Brian Hogan. اقرأ أيضًا المقال السابق: تثبيت إطار Ruby on Rails في أوبنتو لينكس الدليل السريع إلى لغة البرمجة روبي Ruby تعرّف على لغة البرمجة Ruby
-
يعد روبي أون ريلز Ruby on Rails -أو اختصارًا ريلز Rails- من أشهر أطر العمل بلغة روبي Ruby المستخدمة في تطوير المواقع وتطبيقات الويب بسهولة وسرعة، إذ يمكن تثبيت إطار العمل باستخدام أداة سطر الأوامر rbenv، التي تتيح إمكانية إنشاء بيئة لتطوير التطبيقات باستخدام روبي أون ريلز والتبديل بين نسخ روبي المختلفة، كما تمكّنك من تخصيص وتحديد نسخة روبي مختلفة لكل تطبيق تطوره على حدى بغض النظر عن النسخة الافتراضية العامة ضمن النظام، كما تمكّنك من تحديد نسخة روبي محددة لكل مستخدم على حدى مع ترك إمكانية تغيير تلك النسخة باستخدام متغيرات البيئة. سنتعرف في هذا المقال على طريقة تثبيت روبي أون ريلز باستخدام rbenv و gem، إذ سنبدأ بتثبيت الحزم المطلوبة لعمل rbenv ثم روبي وبعدها سنثبت الإضافة ruby-build لنتمكن من تثبيت أحد إصدارات روبي المتوفرة. بعد ذلك، سنستخدم gem لتثبيت إطار ريلز لنتمكن من استخدام روبي ضمنه من أجل تطوير تطبيقات الويب، وسنتعلم أيضًا طريقة التحقق من توفر التحديثات لنسخة rbenv المثبتة، وطريقة إزالة تثبيت نسخ روبي وأيضًا rbenv. ستحتاج لتطبيق الخطوات في هذا المقال إلى ما يلي: نظام خادم أوبنتو 22.04 مثبت مع إمكانية الوصول لحساب مستخدم يمكنه تنفيذ الأمر sudo وجدار ناري مثبت. نسخة نود Node.js مثبتة على النظام، إذ تحتاج بعض مزايا ريلز، مثل ميزة أنبوب الموارد Asset Pipeline لبيئة تشغيل جافا سكريبت لعملها. تثبيت أداة rbenv واعتماديتها تعتمد لغة روبي على عدد من الحزم التي يمكنك تثبيتها من خلال مدير الحزم، وحالما تثبّت هذه الحزم، يمكنك تثبيت rbenv واستخدامها لتثبيت روبي. نبدأ بتنفيذ أمر تحديث قائمة الحزم على النظام: $ sudo apt update ثم نثبّت الاعتماديات المطلوبة تثبيت روبي: $ sudo apt install git curl libssl-dev libreadline-dev zlib1g-dev autoconf bison build-essential libyaml-dev libreadline-dev libncurses5-dev libffi-dev libgdbm-dev بعد ذلك، يمكننا تثبيت rbenv باستخدام الأمر curl لتنزيل النص البرمجي المستخدم لتثبيته الموجود على غيت هب GitHub وتمريره مباشرةً إلى الأمر bash لتنفيذه على النحو التالي: $ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash نضيف المسار "rbenv/bin./~" إلى المتغير PATH$ لنتمكن من استخدام أداة سطر الأوامر rbenv، وذلك بتعديل الملف bashrc./~ ليُطبق التعديل ضمن جلسات الطرفية اللاحقة: $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc ثم نضيف أمر تحميل rbenv إلى نفس الملف مجددًا على النحو التالي: $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc ولتطبيق تلك التعديلات على جلسة الطرفية الحالية ننفذ الأمر التالي: $ source ~/.bashrc نستخدم الأمر type لنتأكد من تطبيقها وتوفر الأمر rbenv، إذ سيعرض هذا الأمر معلومات حول rbenv على النحو التالي: $ type rbenv ليظهر لنا الخرج التالي: rbenv is a function rbenv () { local command; command="${1:-}"; if [ "$#" -gt 0 ]; then shift; fi; case "$command" in rehash | shell) eval "$(rbenv "sh-$command" "$@")" ;; *) command rbenv "$command" "$@" ;; esac } وبذلك نكون ثبتنا كلًا من الأداتين rbenv و ruby-build، وسنبدأ في الفقرة التالية بتثبيت روبي. تثبيت روبي باستخدام ruby-build بعد أن ثبتنا الإضافة ruby-build، يمكننا تثبيت أي نسخة من روبي نحتاجها عبر سطر الأوامر ونبدأ بعرض النسخ المتاحة بتنفيذ الأمر التالي: $ rbenv install -l ليظهر لنا قائمة بالنسخ المتوفرة: 2.7.7 3.0.5 3.1.3 3.2.0 jruby-9.4.0.0 mruby-3.1.0 picoruby-3.0.0 truffleruby-22.3.1 truffleruby+graalvm-22.3.1 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions. لنختار نسخة روبي 3.2.0 ونثبتها كما يلي: $ rbenv install 3.2.0 تستغرق عملية التنزيل والتثبيت بعض الوقت لإنهائها، وبعد ذلك يمكننا تعيين تلك النسخة على أنها نسخة افتراضية باستخدام الأمر الفرعي global على النحو التالي: $ rbenv global 3.2.0 ونتحقق من ذلك بعرض نسخة روبي الحالية: $ ruby -v ليظهر لنا معلومات النسخة التي اخترناها سابقًا على النحو التالي: $ ruby 3.2.0 (2022-12-25 revision a528908271) [x86_64-linux] وبذلك نكون قد ثبتنا نسخة من نسخ روبي وعيناها على أنها نسخة عامة افتراضية وأصبحت جاهزة للعمل عليها. سنعد في الفقرة التالية مكتبة gems وإطار ريلز التعامل مع المكتبات Gems الطريقة التي تنشر بها مكتبات روبي تسمى gems ويمكن استخدام الأمر gem لإدارتها وأيضًا لتثبيت إطار ريلز، فعند تثبيت أي مكتبة منها ينشأ لها ملفات توثيق محلي، وقد تستغرق تلك العملية وقتًا لذا يمكننا إلغاؤها بإنشاء ملف إعدادات في المجلد "gemrc./~" وإضافة تعليمة ضمنه بتنفيذ الأمر التالي: $ echo "gem: --no-document" > ~/.gemrc ونبدأ بتثبيت أول أداة وهي Bundler، التي تفيد في إدارة الاعتماديات ضمن المشروع ويعتمد عليها إطار ريلز في عمله: $ gem install bundler ليظهر لنا الخرج التالي: Fetching bundler-2.4.5.gem Successfully installed bundler-2.4.5 1 gem installed يمكننا استخدام الأمر gem env (الأمر الفرعي env هو اختصار للأمر environment)، لعرض معلومات عن البيئة والاعدادات الحالية للمكتبات gems، إذ يمكننا مثلًا عرض مسار تثبيت الاعتماديات على النظام بتنفيذ الأمر الفرعي home على النحو التالي: $ gem env home ليظهر لنا المسار على النحو التالي: /home/hassan/.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0 بعد أن ثبتنا gems يمكننا الآن تثبيت إطار ريلز وهو ما سنشرحه في الفقرة التالية. تثبيت ريلز لتثبيت ريلز نُنفذ الأمر gem install مع الخيار v- لتحديد الإصدار المطلوب إذ سنستخدم في هذا المقال الإصدار 7.0.4: $ gem install rails -v 7.0.4 يُثبت الأمر السابق المكتبة التي حددناها مع اعتمادياتها، وبما أن ريلز إطار عمل لتطوير تطبيقات الويب المعقدة لذا فهو يحتوي على العديد من الاعتماديات، وهو سبب استغراق تنفيذ الأمر السابق بعض الوقت وسينتج عنه رسالة تفيد بتثبيت ريلز بنجاح مع اعتمادياته: ... Successfully installed rails-7.0.4 35 gems installed يمكننا استعراض الإصدارات المتاحة لاختيار تثبيت إصدار مختلف من ريلز عن طريق البحث باستخدام الأمر search، الذي سيعرض قائمة بالإصدارات المتاحة للتنزيل، ويمكننا الاختيار منها وتثبيت أحد تلك الإصدارات، ففي الأمر التالي سنختار الإصدار 4.2.7: $ gem search '^rails$' --all $ gem install rails -v 4.2.7 وإذا أردنا تثبيت أحدث إصدار من ريلز يمكننا تنفيذ نفس الأمر لكن بدون تحديد رقم الإصدار: $ gem install rails إذ سيُنشئ rbenv مجلدًا لملفات shims، الذي يشير إلى الملفات المستخدمة من قبل إصدار روبي المُفعل حاليًا. يدير rbenv من خلال الأمر الفرعي rehash ملفات shims ضمن ذلك المجلد، وذلك لمطابقة كل أمر من أوامر روبي عبر كل إصدار مثبت من روبي على الجهاز الخاص بنا. عند تثبيت إصدار جديد من روبي أو مكتبة gem توفر أوامرًا يمكن تنفيذها مثل ريلز، يجب تنفيذ الأمر التالي: $ rbenv rehash للتحقق من تثبيت ريلز بنجاح يمكنك طباعة رقم إصداره باستخدام الأمر التالي: $ rails -v فإذا انتهى التثبيت بنجاح سيظهر لنا رقم إصدار ريلز المُثبت: Rails 7.0.4 الآن، يمكننا البدء في اختبار وتثبيت روبي ضمن ريلز وتطوير تطبيقات الويب باستخدامه، وسنستعرض في الفقرة التالية طريقة تحديث rbenv لآخر نسخة متوفرة. تحديث rbenv بما أننا ثبتنا rbenv يدويًا باستخدام Git، يمكننا تحديثه لآخر إصدار باستخدام الأمر git pull ضمن المجلد rbenv./~ على النحو التالي: $ cd ~/.rbenv $ git pull وبذلك نتأكد من استخدام أحدث إصدار متاح من rbenv. إلغاء تثبيت إصدارات روبي قد تتراكم الإصدارات الإضافية التي قد ننزلها من روبي ضمن المجلد "rbenv/versions./~"، ولحل تلك المشكلة يمكننا استخدام الأمر الفرعي uninstall الذي توفره الإضافة ruby-build لإزالة الإصدارات السابقة التي لا نحتاج لها، فمثلًا يمكن استخدام الأمر التالي لإلغاء تثبيت إصدار روبي بنسخة 3.2.0: $ rbenv uninstall 3.2.0 يفيد استخدام أمر إلغاء تثبيت الذي يوفره rbenv بتنظيف ذلك المجلد وإزالة الإصدارات القديمة من روبي لنبقي فقط على ما نحتاج لاستخدامه حاليًا. إلغاء تثبيت rbenv يمكننا إزالة rbenv من نظام التشغيل في حال عدم الحاجة لاستخدامه، وذلك بفتح الملف "bashrc./~" ضمن أي محرر للنصوص مثل نانو nano على النحو التالي: $ nano ~/.bashrc ونبحث عن السطرين التاليين ونحذفهما من الملف: ... export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" بعد حذف هذين السطرين نحفظ الملف ونخرج من المحرر بالضغط على المفتاحين "CTRL + X" ثم "Y" و "Enter" لتأكيد حفظ الملف، ويمكننا بعدها إزالة rbenv وجميع إصدارات روبي المثبتة باستخدام الأمر التالي: $ rm -rf `rbenv root` ستطبق هذه التغييرات ضمن الجلسة بعد تسجيل الخروج وتسجيل الدخول مرة أخرى. الخاتمة تعلمنا في هذا المقال كيفية تثبيت rbenv والأمر gem لتثبيت إطار عمل روبي أون ريلز كاملًا، وبذلك يمكننا إنشاء مشاريع تطوير تطبيقات الويب الجديدة. ترجمة -وبتصرف- للمقال How To Install Ruby on Rails with rbenv on Ubuntu 22.04 لأصحابه Brian Hogan و Lisa Tagliaferri و Jeanelle Horcasitas. اقرأ أيضًا المقال السابق: تثبيت روبي وإعداد بيئة برمجة محلية في أوبنتو لينكس مدخل إلى إطار العمل Ruby on Rails كل ما تحتاج إليه لتعلم البرمجة بـ Ruby توثيق روبي أون ريلز العربي
-
يمكنك استخدام لغة البرمجة الديناميكية روبي Ruby في عدة مجالات، مثل النصوص البرمجية وتطوير الألعاب وتطبيقات الويب، وعن تاريخ هذه اللغة فقد جرى إصدارها عام 1993 في اليابان واشتُهرت في عام 2005 على أنها لغة برمجة تطبيقات الويب بطرف الخادم، إذ صُممت روبي على أنها لغة سهلة الاستخدام خصوصًا للمبتدئين وقوية في نفس الوقت قادرة على إنشاء الأنظمة المعقدة، لذا فمهما كان مستواك البرمجي فتجربة هذه اللغة تستحق الاهتمام. سنتعرف في هذا المقال على إحدى أسهل طرق تثبيت روبي ضمن نظام التشغيل أوبنتو Ubuntu من لينكس باستخدام مدير إصدارات روبي Ruby Version Manager ويختصر إلى RVM، الذي يساعدنا في تنزيل وتثبيت آخر نسخة من روبي مع المكتبات التي تحتاجها، إذ سنجهز بيئةً برمجيةً محلية على جهاز بنظام تشغيل لينكس باستخدام سطر الأوامر ونختبرها بكتابة برنامج روبي بسيط، ويمكنك تطبيق نفس الخطوات في هذا المقال ضمن أي توزيعة لينكس ديبيان Debian أخرى، كما يمكنك متابعة الخطوات وتنفيذها على نظام تشغيل أوبنتو 22.04 والتأكد من تنفيذ الأوامر وذلك من خلال حساب بصلاحيات مدير والتأكد من وجود اتصال بالإنترنت على الجهاز. استخدام الطرفية سنستخدم سطر الأوامر command line لتنفيذ الأوامر وتثبيت روبي، وهي واجهة نصية تمكنك من التعامل مع جهازك بكتابة أوامر نصية وظهور نتيجتها نصيًا أيضًا، وهي بديلٌ عن الواجهة المرئية التي نستخدم فيها مؤشر الفأرة للضغط على الأزرار والتطبيقات للتعامل مع الحاسوب. يُدعى سطر الأوامر أيضًا باسم الصدفة shell، التي تتيح لنا أتمتة العديد من المهام اليومية على الحاسوب وهي من أهم الأدوات التي يجب على المبرمج إتقانها، وسنبدأ في الفقرة التالية باستخدامها لتثبيت مدير إصدارات روبي RVM والذي سيُثبت روبي وكل ما يلزمها. تثبيت RVM وروبي لنبدأ بتثبيت RVM والذي سيؤتمت عملية إعداد بيئة روبي ضمن نظام أوبنتو، إذ أن أسهل وأسرع طريقة لتثبيته هي عبر تنفيذ النص البرمجي المتاح على موقع RVM، إذ يمكنك باستخدام الأمر gpg جلب المفتاح العام من خادم RVM والذي يُستخدم لتوقيه كل إصدار جديد من RVM، ما يتيح إمكانية التحقق من أن إصدار RVM الذي نحاول تنزيله هو إصدار أصلي وصحيح، ويمكنك ذلك بتنفيذ الأمر التالي: $ gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB وفي حال حدوث مشاكل بتنفيذ الأمر السابق يمكنك تجربة الأمر التالي: $ command curl -sSL https://rvm.io/mpapis.asc | gpg2 --import - $ command curl -sSL https://rvm.io/pkuczynski.asc | gpg2 --import - يمكن باستخدام الأمر curl تنزيل النص البرمجي لتثبيت RVM، وسنبدأ أولًا بتثبيت ذلك الأمر في حال لم يكن مثبتًا: $ sudo apt-get install curl عند تنفيذه، ستُسأل عن كلمة مرور حسابك قبل التثبيت، وستلاحظ عند كتابة كلمة المرور عدم ظهور المحارف التي تكتبها ضمن الطرفية وهي ميزة أمان لإخفاء كلمات المرور عندما تدخلها ضمن الطرفية، لذا يمكنك كتابة الكلمة بصورةٍ طبيعية حتى لو لم ترى المحارف التي تدخلها، ثم اضغط المفتاح Enter ليُثبت البرنامج. بعدها سنستخدم curl لتنزيل النص البرمجي لتثبيت RVM من موقع الخاص به من خلال الأمر التالي، ولاحظ محرف \ في بداية الأمر وهو ضروري لتنفيذ الأمر الأصلي وليس أي تسميات لأوامر أخرى تطابقه: $ \curl -sSL https://get.rvm.io -o rvm.sh الخيارات المُمررة للأمر السابق مهمة لذلك سنشرح كل منها على النحو التالي: الخيار s- اختصارًا للأمر silent--: يخبر الأمر curl ألا يظهر شريط تقدم عملية إرسال الطلب. الخيار S- اختصارًا للأمر show-error--: يخبر الأمر curl بإظهار رسائل خطأ فشل الطلب. الخيار L- اختصارًا للأمر location--: يخبر الأمر curl بمعالجة طلبات إعادة التوجيه التي قد يرسلها الخادم في حالات انتقال الملف المطلوب لعنوان آخر، إذ سيعيد إرسال نفس الطلب للعنوان الجديد. يمكنك معاينة محتوى النص البرمجي بعد انتهاء التنزيل بتنفيذ الأمر التالي: $ less rvm.sh يمكنك استخدام الأسهم التنقل وتصفح محتوى ذلك الملف، وبعد الانتهاء يمكنك الضغط على q للخروج والرجوع إلى الطرفية، والآن يمكنك تنفيذه لتثبيت آخر إصدار مستقر من RVM على النحو التالي: $ cat rvm.sh | bash -s stable سينتج عن تنفيذ هذا الأمر إنشاء مجلد جديد بالاسم "rvm." ضمن المجلد الأساس home للمستخدم الحالي، إذ ستُثبت ضمنه روبي وكل المكونات التي تحتاجها مع البرنامج التنفيذي rvm الذي سنستخدمه لتثبيت روبي، بحيث سيُضاف للملف "bashrc." المسار "rvm/bin." ضمن متغير البيئة PATH وذلك لنتمكن من تنفيذ الأمر rvm عبر سطر الأوامر بسهولة، ولتفعيل تلك التعديلات يجب تنفيذ الأمر التالي أولًا: $ source ~/.rvm/scripts/rvm والآن يمكننا تنفيذ الأمر rvm لتثبيت آخر نسخة من روبي على النحو التالي: $ rvm install ruby --default لتُنزّل وتُثبت بذلك روبي ومكوناتها وتُعيّن تلك النسخة على أنها نسخة افتراضية ضمن النظام لاستخدامها لتجنب التعارض مع أي نسخة أخرى قد تكون مُثبتة مسبقًا على جهازك. قد تستغرق هذه العملية بعض الوقت، وفي حال واجهت مشاكل تأكد من تثبيت Homebrew على جهازك بتنفيذ الأمر التالي: $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" سيُثبت الأمر أي متطلبات ناقصة لديك وقد يسألك أيضًا عن كلمة المرور لحسابك، وسيظهر عن تنفيذ أمر تثبيت روبي خرجًا مشابهًا للتالي: Searching for binary rubies, this might take some time. Found remote file https://rvm_io.global.ssl.fastly.net/binaries/ubuntu/16.04/x86_64/ruby-2.4.0.tar.bz2 Checking requirements for ubuntu. Installing requirements for ubuntu. Updating system.... Installing required packages: gawk, libssl-dev, zlib1g-dev, libyaml-dev, libsqlite3-dev, sqlite3, autoconf, libgmp-dev, libgdbm-dev, libncurses5-dev, automake, libtool, bison, libffi-dev, libgmp-dev, libreadline6-dev.................. Requirements installation successful. وبعد الانتهاء من تثبيت المتطلبات سيبدأ RVM عملية تثبيت روبي ويظهر خرجًا مشابهًا للتالي: ruby-2.4.0 - #configure ruby-2.4.0 - #download % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 16.4M 100 16.4M 0 0 4828k 0 0:00:03 0:00:03 --:--:-- 4829k ruby-2.4.0 - #validate archive ruby-2.4.0 - #extract ruby-2.4.0 - #validate binary ruby-2.4.0 - #setup ruby-2.4.0 - #gemset created /home/brian/.rvm/gems/ruby-2.4.0@global ruby-2.4.0 - #importing gemset /home/brian/.rvm/gemsets/global.gems.............................. ruby-2.4.0 - #generating global wrappers........ ruby-2.4.0 - #gemset created /home/brian/.rvm/gems/ruby-2.4.0 ruby-2.4.0 - #importing gemsetfile /home/brian/.rvm/gemsets/default.gems evaluated to empty gem list ruby-2.4.0 - #generating default wrappers........ بذلك تكون قد ثُبتت آخر نسخة من روبي لديك مع بعض الأدوات الأخرى، مثل irb وهي طرفية روبي تفاعلية سنتعرف عليها في مقال لاحق من هذه السلسلة، والبرنامج rake لأتمتة تنفيذ النصوص البرمجية، والبرنامج gem، الذي سنستخدمه لتثبيت وتحديث مكتبات روبي بكل سهولة ضمن المشاريع. للتأكد من رقم النسخة المُثبتة من روبي، ننفذ الأمر التالي: $ ruby -v سيظهر رقم نسخة روبي المثبتة لديك بخرج يشبه ما يلي: ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux] قبل تجربة روبي، يجب التأكد أن الطرفية عند فتحها تُنفذ صدفة تسجيل دخول، إذ أن RVM عدل الملف bash_profile. لتمكين استخدام نسخة روبي الذي ثبتها وذلك فقط ضمن صدفة تسجيل الدخول. سنختبر في الفقرة التالية روبي ببرنامج بسيط جدًا. كتابة برنامج بسيط كما هو معتاد عند تجربة لغة برمجة جديدة فأول برنامج نكتبه مهمته طباعة النص "Hello, World" لنتأكد بذلك من صحة تثبيت كل شيء وإمكانية تنفيذ البرامج بتلك اللغة، لذا نبدأ بإنشاء ملف جديد بالاسم hello.rb باستخدام محرر نانو nano: $ nano hello.rb ونكتب التعليمة التالية ضمن الملف: puts "Hello, World!" ثم نخرج من المحرر بالضغط على "Ctrl+X" ثم نضغط على المفتاح "Y" لتأكيد حفظ الملف، ثم ننفذ ملف البرنامج على النحو التالي: $ ruby hello.rb ليظهر لنا الخرج المطلوب ضمن الطرفية: Hello, World! بذلك تأكدنا أن بيئة برمجة روبي تعمل بنجاح ويمكننا استخدامها لتطوير البرامج والمشاريع باستخدام هذه اللغة. الخاتمة بعد أن تعرفنا على طريقة تجهيز بيئة للبرمجة بلغة روبي، سنتعرف في المقالات التالية أكثر على تلك اللغة عبر تجربة كتابة أول برنامج بلغة روبي، إذ أن أفضل طريقة لتعلم أي لغة برمجية هي بكتابة البرامج عمليًا وتنفيذها. ترجمة -وبتصرف- للمقال How To Install Ruby and Set Up a Local Programming Environment on Ubuntu 22.04 لصاحبته Milecia McGregor. اقرأ أيضًا كيف تثبّت Ruby على ويندوز 10 الدليل السريع إلى لغة البرمجة روبي Ruby توثيق لغة روبي
-
عند التعرف على موضوع جديد والقراءة عنه نبدأ عادة بالبحث عن تعريف يشرح ويوضح الصورة العامة له، ثم نكتشف مجموعة من المواضيع الأخرى الفرعية المتعلقة بالموضوع الأساسي، وعادة ما نبدأ البحث عبر الإنترنت عبر ويكيبيديا مثلًا، ونقرأ ونستكشف المواضيع بين صفحة وأخرى. لكن ماذا لو كان هناك أداة تساعدنا في ذلك فتولد مواضيع فرعية من الموضوع الرئيسي أو المواضيع الفرعية عنه وتشرح كل منها، هذا ما سنطوره في هذا المقال ومصدر تلك المعلومات سيكون أحد نماذج الذكاء الاصطناعي وهو ChatGPT. هل يملك ChatGPT المعرفة تٌحاكي نماذج اللغة الكبيرة (أو Large Language Models) ومنها ChatGPT البيانات التي دُّربت عليها، فإذا دُربت على نصوص متنوعة ستتمكن من محاكاة وتوليد نصوص مشابهة لها قد لا يُلاحظ الفرق بينها وبين الأصلية، ولكن لا يمكن الاعتماد على دقتها فتلك النماذج خصوصًا المُدربة على طيف واسع من البيانات تُحاكي فقط ما دُربت عليه ولا تملك فهم حقيقي كما نحن البشر عن تلك النصوص. فكثيرًا من الأحيان سيخطئ النموذج بثقة، لذا لا يمكننا الاعتماد دومًا على معلوماته، إنما في هذا المقال سنطور تطبيق لتوليد معلومات عامة وبسيطة ليست دقيقة أو معقدة، فمثل تلك المعلومات قد وردت ضمن البيانات التي دُرب عليها ChatGPT كثيرًا ومن أكثر من رأي وزاوية، لذا يمكننا الاعتماد عليه في حال كانت المعلومات بسيطة وغير معقدة مع الحذر دومًا والتحقق من صحة المعلومات الحساسة. أفكار لمشاريع باستخدام ChatGPT بسبب سهولة التعامل مع نموذج ChatGPT والطيف الواسع من الإمكانيات يمكن الاستفادة من مثل تلك النماذج في بناء العديد من التطبيقات بأفكار متنوعة، فمثلًا تلخيص مقال طويل يستفيد منه المستخدم في الاطلاع على محتوى المقال بسرعة قبل قراءته كما فعلنا في تطبيق لخصلي في مقال سابق، أو يمكن طلب اقتراح وصفة طعام معينة وشرح طريقة تحضيرها والاستفادة من المعلومات الكامنة ضمن النموذج والذي اكتسبها أثناء مرحلة التدريب كما فعلنا في تطبيق وصفة في مقال آخر سابق، أو يمكن طلب تصحيح نص ما أدخله المستخدم والاستفادة منه كمصحح نصوص آلي، أو يمكن طلب استخراج الكلمات المفتاحية من نص معين والذي يفيد في العديد من المجالات من تخزين البيانات إلى مساعدة كُتّاب المقالات وغيرها. أما في هذا المقال سنتعرف على طريقة الاستعانة بنموذج ChatGPT لتوليد مواضيع في مجالات مختلفة والغوص في مواضيع فرعية عنها، وسوف نستفيد من المعرفة التي يملكها ChatGPT وتدرب عليها مسبقًا، وذلك بتطوير تطبيق بسيط يعرض للمستخدم بعض الأفكار العشوائية ليختار أحدها ثم نتفرع إلى مواضيع فرعية مع شرح بسيط عنها، ثم سنجعل ChatGPT يختار موضوع فرعي لنولد منه مواضيع فرعية أيضًا وهكذا يستفيد المستخدم من التطبيق في استكشاف الأفكار وتفاصيلها. دورة تطوير التطبيقات باستخدام لغة JavaScript تعلم البرمجة بلغة جافا سكريبت انطلاقًا من أبسط المفاهيم وحتى بناء تطبيقات حقيقية. اشترك الآن تحضير صفحات التطبيق نبدأ بإنشاء مجلد جديد لملفات المشروع ونُنشئ ملف HTML بالاسم index.html ونستورد ضمنه ملف مكتبة jQuery والتي سنستخدمها لتعديل محتويات الصفحة، وملف جافا سكريبت باسم script.js والذي سيحوي شيفرة التطبيق، وملف CSS يتضمن التنسيقات style.css كالتالي: <html lang="ar"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script> <script src="script.js" defer></script> <link rel="stylesheet" href="style.css" /> <title>علمني</title> </head> <body> <div> <h1></h1> <p><span>?</span> اختر أحد المواضيع لمعرفة المزيد</p> </div> <div id="container"> </div> </body> </html> كما تلاحظ في الكود أعلاه تحتوي الصفحة على عنصر <div> بالمٌعرّف container وضمنه سنضيف محتوى المواضيع الأساسية والمواضيع الفرعية. بعد ذلك سوف ننشئ ملف التنسيقات style.css ضمن مجلد المشروع وسنكتب ضمنه التنسيقات التالية: body { direction: rtl; display: flex; flex-direction: column; max-width: 60ch; justify-items: center; align-items: center; font-size: x-large; margin: 0 auto; padding: 20vh 10vw; background-color: #f9f9f9; color: #121212; font-family: system-ui; align-items: stretch; gap: 1rem; } p { line-height: 2; color: darkslategray; } a { padding: 1rem; font-size: inherit; margin: 0 .5rem; color: darkblue; } footer { margin-top: 2rem; } form { display: flex; flex-direction: column; gap: 2rem; text-align: center; } button { background-color: #121212; color: #f9f9f9; border: none; padding: 1rem 2rem; font-size: inherit; cursor: pointer; font-family: inherit; } input { padding: 1rem 2rem; font-size: inherit; font-family: inherit; text-align: center; border: .2rem solid; } .subject { padding: 2rem 2rem; cursor: pointer; border: .15rem solid lightgray; border-radius: .5rem; } #container { gap: 2rem; display: flex; flex-direction: column; } لمعاينة شكل الموقع نضيف بعض المواضيع الوهمية يدويًا ضمن العنصر بالمعرّف container بالشكل التالي: <div class="subject"> <h2>الموضوع</h2> <p>شرح عن الموضوع</p> </div> نفتح الصفحة index.html ضمن المتصفح ونلاحظ شكل الموقع بعد التنسيق: توليد وعرض مواضيع عشوائية للمستخدم ننشئ ملف جافا سكريبت script.js ونبدأ بكتابة شيفرة التطبيق، وللتعامل مع الواجهة البرمجية لنموذج ChatGPT يجب توليد مفتاح الاستيثاق لها بعد إنشاء حساب مدفوع على موقع شركة OpenAI، ولكي نتجنب استخدام المفتاح الخاص بنا من قبل المستخدمين يمكننا الطلب من كل مستخدم إدخال مفتاحه الخاص عند أول استخدام للتطبيق وحفظه لاستخدامه لاحقًا عند الاتصال بالواجهة البرمجية للنموذج ChatGPT، حيث يسمح لنا ذلك بحرية أكبر في تطوير التطبيقات التي تعتمد على الذكاء الاصطناعي بلا أي كلفة. ولاستخدام ذلك المفتاح يجب حفظه في مكان آمن، وبما أننا لن نستخدم أي شيفرات جافاسكربت خارجية فسنخزن المفتاح ضمن التخزين المحلي لمتصفح الويب الخاص بالمستخدم باستخدام الكائن localStorage، أما في حال استخدامك لأي شيفرات من مكتبات خارجية ضمن المشروع فيمكن لتلك الشيفرات قراءة المفتاح وسرقته من المستخدم، عندها يجب البحث عن حلول أخرى كتخزين المفتاح بأمان ضمن خادم المشروع والتعريف عن المستخدم بطريقة ما مثل استخدام ملفات تعريف الارتباط Cookies وإرسال الطلبات من قبل الخادم بدلًا من قبل متصفح المستخدم نفسه، والآن نعرف التابع openAIKey والذي يتحقق وجود مفتاح مُخزن سابقًا ويعيده، وإلا يطلب من المستخدم إدخال مفتاحه باستخدام الدالة prompt وحفظ ذلك المفتاح للاستخدام لاحقًا: function openAIKey() { const localStorageKey = 'OPENAI_API_KEY'; // استخراج المفتاح المُخزن سابقًا let apiKey = localStorage.getItem(localStorageKey) if (!apiKey) { // طلب إدخال المفتاح من المستخدم apiKey = prompt('أدخل مفتاح OpenAI الخاص بك') // تخزين المفتاح localStorage.setItem(localStorageKey, apiKey) } return apiKey } وللتعامل مع نموذج ChatGPT نُعرف التابع chatGPT والذي يقبل التعليمة التي سترسل له كمعامل أول، ويُرسل طلب من نوع POST إلى المسار https://api.openai.com/v1/chat/completions بحسب توثيق تلك الواجهة البرمجية وكما شرحناها في مقال سابق، ثم نستخرج رد النموذج منه كالتالي: function chatGPT(prompt) { // إرسال الطلب return fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', // ترويسة الاستيثاق 'Authorization': 'Bearer ' + openAIKey(), }, body: JSON.stringify({ // اسم النموذج "model": "gpt-3.5-turbo", // تعليمة المستخدم "messages": [{ "role": "user", "content": prompt }] }), }) // تفسير الطلب .then(response => response.json()) // استخراج رد النموذج .then(json => json.choices[0].message.content) } باستخدام التابع chatGPT يمكننا الآن توليد محتوى الأفكار العشوائية الأولية التي ستظهر للمستخدم بإرسال التعليمة التالية إلى ChatGPT: اقترح بعض المواضيع لأتعلم عنها، المواضيع كل موضوع بسطر: لاحظ أننا نحدد في نهاية التعليمة شكل الخرج الذي نريده وهو في هذه الحالة أن يكتب لنا كل موضوع بسطر، لنتمكن من استخراج تلك المواضيع بتقسيم الرد إلى الأسطر المكونة له، ثم تحويل كل سطر إلى كائن جافاسكربت يحتوي على الحقل title وهو عنوان الموضوع، نٌعرّف تلك العملية ضمن التابع getInitialSubjects كالتالي: function getInitialSubjects() { return chatGPT('اقترح بعض المواضيع لأتعلم عنها، المواضيع كل موضوع بسطر:') .then(subjects => subjects.split('\n').map(title => ({ title }))) } ولعرض تلك المواضيع نستخدم مكتبة jQuery لتحديد العنصر الذي سيحتوي المحتوى باستخدام التابع $ وهو صاحب المٌعرّف container، ونفرغ محتواه باستخدام empty ونمر على كل كائن منها ونولد له عناصر تحوي على عنوان الموضوع من الحقل title، ونستخدم التابع append لإضافة تلك العناصر داخل بعضها، ونعرّف عملية عرض كائنات المواضيع ضمن التابع renderSubjects ليكون كالتالي: function renderSubjects(subjects) { // العنصر الحاوي const $container = $('#container') // مسح المحتوى السابق $container.empty() for (const subject of subjects) { // إنشاء عنصر الموضوع const $subject = $(`<div class="subject"></div>`) // إضافة العنوان $subject.append(`<h2>${subject.title}</h2>`) // إضافة الموضوع إلى الحاوية $container.append($subject) } } وبذلك أصبحت كل التوابع جاهزة من توليد الأفكار العشوائية إلى عملية عرضها، لذا في نهاية الملف نستدعي التابع getInitialSubjects ونمرر تابع عرض المواضيع renderSubjects إلى التابع then من ذلك التابع لعرض تلك المواضيع بعد جلبها، ونفتح الملف index.html ضمن المتصفح سنلاحظ طلب التطبيق منا لأول مرة إدخال مفتاح الواجهة البرمجية: [prompt.png] وبعد إدخال المفتاح الخاص بنا سيُرسل طلب توليد المواضيع إلى ChatGPT وتعرض النتائج كالتالي: نلاحظ تنوع المواضيع التي ولدها وكيف أمكننا استخراجها من الرد وعرض كل منها على حدى، وفي الفقرة التالية سنضيف ميزة الدخول إلى مواضيع فرعية من أي موضوع مقترح نريد معرفة المزيد عنه. عرض مواضيع فرعية عن موضوع يمكننا أن نطلب من ChatGPT توليد مواضيع فرعية من موضوع محدد اختاره المستخدم، مع طلب إرفاق شرح بسيط عن كل موضوع فرعي، لذا يجب إرسال تعليمة مختلفة عن السابقة نطلب بها توليد المواضيع الفرعية ونحدد شكل الخرج الذي نريده ليسهل علينا استخراجه من الطلب، والصيغة التي سنرسلها ستكون كالتالي: حيث نستبدل <اسم الموضوع> بعنوان الموضوع الذي اختاره المستخدم، ونلاحظ كيف أرفقنا أمثلة عن شكل الخرج المطلوب وفي نهاية التعليمة نرشد النموذج أن يبدأ مباشرة بتوليد تلك الأفكار بالصيغة المُحددة، ولإرسال تلك التعليمة وتحويل الرد المولّد عنها إلى كائنات بعنوان ضمن الحقل title وشرح عنها ضمن الحقل desc نُعرّف التابع getSubSubjects والذي يقبل كمعامل أول عنوان الموضوع ويستدعي داخله التابع chatGPT ويمرر التعليمة مدرجًا ضمنها الموضوع المٌمرر، ثم يحول الرد إلى كائنات مواضيع بالصيغة المطلوبة كالتالي: function getSubSubjects(subject) { // توليد التعليمة const prompt = `اقترح بعض الأفكار الفرعية عن "${subject}" لأتعلم عنها مع شرح قصير عن كل منها بالصيغة التالية: الفكرة شرح قصير الفكرة شرح قصير الأفكار الفرعية بالصيغة السابقة:` return chatGPT(prompt) // تحويل الرد إلى كائنات .then(subjects => subjects.split('\n\n').map(subject => { const [title, desc] = subject.split('\n') return { title, desc } })) } نريد توليد مواضيع فرعية عند ضغط المستخدم على موضوع ما، لذا نُعدل التابع renderSubjects لإضافة تابع معالجة لحدث الضغط click والذي سيستدعي تابع توليد المواضيع الفرعية getSubSubjects مع تمرير عنوان الموضوع الذي اختاره المستخدم، ثم يمرر النتيجة للتابع نفسه renderSubjects كي تُعرض المواضيع الفرعية بدل الحالية، ونضيف لكل موضوع الشرح من الحقل desc في حال وجوده كالتالي: function renderSubjects(subjects) { ... for (const subject of subjects) { const $subject = $(`<div class="subject"></div>`) // توليد المواضيع الفرعية عند الضغط $subject.on('click', () => getSubSubjects(subject.title).then(renderSubjects)) $subject.append(`<h2>${subject.title}</h2>`) // استخراج الشرح وعرضه if (subject.desc){ $subject.append(`<p>${subject.desc}</p>`) } $container.append($subject) } } تجربة التطبيق أصبح التطبيق جاهزًا بالكامل ويمكن للمستخدم الغوص إلى ما لا نهاية في المواضيع الفرعية عن الفرعية وهكذا، لنختبر ذلك باختيار موضوع العلوم الطبيعية والفيزيائية من القائمة السابقة ونرى النتيجة: ولد مواضيع فرعية مع شرح بسيط عن كل منها عن ذلك الموضوع لندخل مثلًا إلى موضوع الذكاء الاصطناعي: أيضًا أصبحت المواضيع أكثر تخصصًا لنختر تعلم الآلة: دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن الختام يمكن بتوكيل المستخدم بتوليد واستخدام مفاتيحه الخاصة بالسماح للمطور تطوير التطبيقات المعتمدة على الذكاء الاصطناعي وإتاحتها للاستخدام للجميع دون أي كلفة، والتطبيق الذي طورناه في هذا المقال يعتمد على المعرفة الكامنة ضمن نموذج ChatGPT لتوجيه توليد المحتوى الذي نرغب به، ويمكن التطوير على التطبيق بكثير من الأفكار فمثلًا إضافة رابط عودة للبداية، أو عرض المواضيع الفرعية تحت الموضوع التابعة له ليصبح التطبيق عبارة عن شجرة يمكن فردها إلى ما لا نهاية، أو إضافة زر لتوليد شرح أطول عن موضوع معين أو طلب سؤال محدد عن ذلك الموضوع، ونرى أن الأفكار لا حصر لها بسبب سهولة التعامل مع هذه النماذج باستخدام اللغة الطبيعية سواء ضمن التطبيق أو من دخل المستخدم. اقرأ أيضًا تطوير تطبيق 'وصفة' لاقتراح الوجبات باستخدام ChatGPT و DALL-E في PHP تطوير تطبيق 'اختبرني' باستخدام ChatGPT ولغة جافاسكربت مع Node.js الذكاء البشري مقابل الذكاء الاصطناعي
-
نماذج اللغة الكبيرة (أو Large Language Models) هي نوع من نماذج الذكاء الاصطناعي التي تُدرّب على كمية كبيرة من النصوص لتستطيع بذلك توليد نصوص مشابهة لها، وهنا يأتي ChatGPT وهو نموذج لغة كبير دُرّب على محتوى كبير جدًا من الانترنت، بحيث أصبح لديه اطلاع على الكثير من المعلومات واللغات وأساليب الكتابة، وطوّر باستخدام بعض التقنيات أهمها تقنية التعلم الموجه من قبل البشر RLHF (أو Reinforcement learning from human feedback) ليتّبع في توليده للنصوص اسلوب الحوار بين المستخدم والنموذج، مما يبسط طريقة التعامل معه وتوجيهه لتوليد النصوص التي نحتاحها، وفي هذا المقال سنتعامل مع ذلك النموذج لتوليد نصائح وحكم مفيدة للمستخدم بحسب ما يطلب أو يشعر، وذلك عبر واجهة سطر الأوامر نطورها ضمن بيئة نود (Node.js) حيث يُدخل المستخدم موضوع النصيحة أو الهدف منها ويولد ChatGPT ذلك، وستحتاج لاتباع المقال معرفة بلغة جافاسكربت وبيئة نود ومدير الحزم npm. إنشاء برنامج سطر أوامر باستخدام npm كي نتمكن من تنفيذ التطبيق بسهولة من سطر الأوامر كأي برنامج آخر مثبت على الجهاز وبدلًا من تشغيله كل مرة باستخدام نود Node.js والإشارة إلى مسار ملف جافاسكربت الرئيسي للبرنامج يمكننا الاستفادة من ميزة في npm تمكننا من تحديد ملف جافاسكربت من حزمة ما كالمشروع الذي سنطوره ونشره ضمن الجهاز كبرنامج ضمن سطر الأوامر، حيث يُستخدم مدير الحزم npm عادة لتعريف البرامج في نود كحزم يمكن استخدامها ضمن المشاريع الأخرى، ولنبدأ أولًا بإنشاء مجلد جديد لملفات المشروع وليكن بالاسم inspire-cli ونعرفه كحزمة نود بتنفيذ الأمر التالي ضمن المجلد: npm init -y سينشئ عن تنفيذ ذلك الأمر ملف تعريف الحزمة package.json ضمن المجلد، وباستخدام الخيار y- ستُملئ القيم الافتراضية ضمنه دون الحاجة لإدخالها يدويًا، والآن نُنشئ ملف البرنامج الرئيسي index.js والذي سيحتوي شيفرة البرنامج، ولتعريفه كبرنامج سطر أوامر يمكن بداخل ملف تعريف الحزمة package.json وضمن المفتاح bin تعريف كائن مفاتيحه هي أسماء برامج سطر الأوامر التي توفرها هذه الحزمة، وقيمها هي مسار الملفات التي ستُنفذ باستخدام نود عند تنفيذ البرنامج، لتطبيقنا سنعرف برنامج بالاسم ألهمني ومسار ملف البرنامج له هو الملف الرئيسي index.js كالتالي: "bin": { "ألهمني": "index.js" } وضمن ملف جافاسكربت الرئيسي لذلك البرنامج index.js نضيف السطر التالي لاختبار نجاح التنفيذ، ونلاحظ سطر البداية وهو سطر يدل مدير الحزم npm على الطريقة الصحيحة لإنشاء الملف التنفيذي لبرنامج سطر الأوامر المقابل لهذا الملف لتحديد بيئة تشغيله وهي نود: #!/usr/bin/env node console.log('مرحبًا') وبذلك يمكن لأي مستخدم بعد تثبيت هذا المشروع كحزمة على جهازه تنفيذ الأمر باستخدام اسم البرنامج المُعرف ضمن الكائن bin ليُنفذ البرنامج index.js في بيئة نود ويظهر له النتيجة، ولاختباره أثناء التطوير يمكن الاستفادة من أمر الربط link من مدير الحزم npm والذي يربط الحزمة ضمن البيئة العامة للجهاز لنتمكن من استخدامها من أي مكان، لذا ننفذ الأمر التالي ضمن مجلد المشروع: npm link . تدل النقطة على المشروع الذي نرغب بربطه وتعني المشروع في المسار الحالي، وبعد تنفيذ هذا الأمر أصبح الوصول لبرنامج سطر الأوامر ممكنًا من أي مكان لنختبر ذلك بتنفيذ الأمر التالي ضمن أي طرفية terminal لنلاحظ خرج البرنامج كالتالي: أصبح برنامج سطر الأوامر جاهزًا وسنبدأ في الفقرة التالية بتطويره ليستقبل الدخل من المستخدم عبر معاملات سطر الأوامر المُمررة له. دورة تطوير التطبيقات باستخدام لغة JavaScript تعلم البرمجة بلغة جافا سكريبت انطلاقًا من أبسط المفاهيم وحتى بناء تطبيقات حقيقية. اشترك الآن استقبال الدخل من المستخدم نحتاج أن يدخل المستخدم موضوع النصيحة أو الحكمة التي يرغب بها، ويمكن استقبال ذلك عبر معاملات سطر الأوامر التي يمررها المستخدم عند تنفيذ البرنامج، ويمكن الوصول إليها في نود عبر المتغير العام process.argv وهو مصفوفة من القيم أول قيمة منه هي مسار البرنامج التنفيذي لبيئة نود التي ستُنفذ البرنامج، والقيمة الثانية هي مسار ملف البرنامج الحالي الذي سيُنفذ، والقيم الباقية هي ما تُهمنا وهي جميع المعاملات المُمررة للبرنامج من قبل المستخدم عند تنفيذه، حيث سنجمعها معًا لنكون منها الجملة التي أدخلها المستخدم لتحضيرها قبل إرسالها، لذا نعدل ملف البرنامج index.js ليصبح كالتالي: #!/usr/bin/env node const args = process.argv.slice(2) const input = args.join(' ') حيث باستخدام التابع slice على مصفوفة المعاملات المُمررة للبرنامج يمكننا تجاهل أول عنصرين منها واستخراج المعاملات المُمررة فقط، وباستخدام التابع join نجمع تلك الكلمات ونفصل بينها بفراغات لتكون قيمة الثابت input هي جملة الدخل من المستخدم، وفي الفقرة التالية سنُرسل تلك الجملة إلى نموذج ChatGPT لتوليد النصيحة المطلوبة. إرسال الدخل إلى ChatGPT وتوليد النصيحة لنتمكن من التعامل مع نموذج ChatGPT عبر الواجهة البرمجية API له يجب إنشاء حساب جديد ضمن المنصة المالكة له OpenAI وإعداد الحساب كحساب مدفوع وإدخال وسيلة الدفع، بعد ذلك يمكن توليد مفتاح للتعامل مع الواجهة البرمجية نتأكد من نسخه والاحتفاظ به، وننفذ الأمر التالي مع تبديل قيمة المفتاح لتخزينه ضمن متغيرات البيئة لجلبه ضمن البرنامج لاحقًا: set OPENAI_API_KEY=<قيمة المفتاح> وضمن نظام ماك أو لينكس يمكن تنفيذ الأمر التالي: export OPENAI_API_KEY=<قيمة المفتاح> وبذلك عند تنفيذ البرنامج من نفس هذه الطرفية سيتمكن البرنامج من الوصول لقيمة ذلك المفتاح دون الحاجة لكتابته ضمن الشيفرة المصدرية، والآن لنرسل دخل المستخدم ونطلب من نموذج ChatGPT الرد المناسب ويمكن ذلك بحسب التوثيق الرسمي لواجهة النموذج البرمجية بإرسال طلب HTTP من نوع POST إلى مسار الواجهة البرمجية https://api.openai.com/v1/chat/completions ونضع مفتاح الواجهة ضمن الترويسة Authorization وقيمتها الكلمة Bearer ثم مسافة ثم قيمة المفتاح، وضمن جسم الطلب نحدد كقيمة للمفتاح model اسم النموذج الذي نرغب بالتعامل معه وهو gpt-3.5-turbo وهو الاسم التقني لنموذج ChatGPT بالإصدار الثالث، ونضيف كقيمة للمفتاح messages مصفوفة من الكائنات المُعبرة عن الرسائل المُرسلة للنموذج نرسل ضمنها رسالة المستخدم وهو الدخل المُمرر للبرنامج، ويمكننا توجيه النموذج لشكل الخرج الذي نريده عبر تشكيل رسالة المستخدم لتصبح بالشكل التالي: const message = `ألهمني بعبارة مختصرة عن الموضوع التالي: ${input}` وبذلك نرشد النموذج داخل البرنامج لطبيعة الرد الذي نريده منه دون أن يحتاج المستخدم سوى لإدخال الموضوع الذي يريده للبرنامج، وباستخدام fetch يمكننا إرسال طلب HTTP للواجهة البرمجية للنموذج كالتالي: fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${proccess.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ "model": "gpt-3.5-turbo", "messages": [{ "role": "user", "content": message }] }), }) حيث يعيد التابع fetch وعدًا (أو Promise) يمكننا استخدام التابع then للحصول على الرد، ونحول الرد القادم إلى صيغة json ثم نستخرج منها محتوى جواب النموذج على الرسالة المرسلة من قبل المستخدم وهي ضمن القيمة json.choices[0].message.content من رد json المرسل ونعرضها له على الشاشة كالتالي: fetch(...) .then(r => r.json()) .then(json => json.choices[0].message.content) .then(gpt_response => console.log(gpt_response)) وبذلك يصبح البرنامج جاهزًا لنختبره في الفقرة التالية لتوليد بعض النصائح المفيدة. اختبار البرنامج ضمن سطر الأوامر وبما أننا عرفنا اسم البرنامج بالاسم ألهمني يصبح تنفيذ البرنامج أشبه بكتابة جملة معبرة باللغة العربية، وفي كل مرة سيُولد النموذج نصيحة مختلفة بحسب الموضوع المحدد، فمثلًا لنرى ما لديه من نصائح حول النجاح بتنفيذ الأمر ألهمني عن النجاح: النجاح هو النتيجة الإيجابية للجهود المستمرة والتفاني في العمل لتحقيق الأهداف. لنرى ما سينصح به شخص يشعر بالتعب بتنفيذ الأمر ألهمني أشعر بالتعب: استرح قليلاً، واشرب ماء بكميات كافية، ومارس التمارين الرياضية بانتظام. نصيحة تقليدية لكن يجب الاهتمام بها، وعند تنفيذ نفس الأمر مرة أخرى حصلنا على نصيحة مختلفة: لا تستسلم للتعب، النجاح يتطلب الجهد والإصرار. لاحظ سهولة التعامل مع هذه النماذج باستخدام اللغة الطبيعية، مما يفتح الاحتمالات الواسعة لتطوير العديد من التطبيقات المفيدة والأفكار الرائعة التي تضيف للمستخدم قيمة مفيدة أو توفر الوقت والجهد في بعض الأحيان، حيث يمكن التطوير على التطبيق الذي طورناه في هذا المقال بالتعديل على التعليمة المرسلة للنموذج لتغيير النتيجة التي يولدها أو اللهجة التي يتكلم بها أو حتى أن يرد بلغة مختلفة أو أي أفكار أخرى تخطر في ذهنك يمكنك مشاركتها في التعليقات ومشاركة النتائج التي حصلت عليها بعد تجربتك تطبيقها. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن اقرأ أيضًا برمجة تطبيق 'لخصلي' لتلخيص المقالات باستخدام ChatGPT ولارافل تطوير تطبيق 'وصفة' لاقتراح الوجبات باستخدام ChatGPT و DALL-E في PHP تطوير تطبيق 'اختبرني' باستخدام ChatGPT ولغة جافاسكربت مع Node.js
-
تتميز نماذج اللغة الكبيرة أو Large Language Models بقدرتها على فهم اللغة الطبيعية وتوليد النصوص الصحيحة إملائيًا والصحيحة بمعناها أيضًا، مما يسمح بالتعامل مع نماذج الذكاء الاصناعي تلك بسهولة وبساطة من خلال اللغة الطبيعية، ومن تلك النماذج نموذج ChatGPT الشهير من شركة OpenAI والذي سنستفيد من مزاياه تلك والمعلومات الكامنة ضمنه والتي اكتسبها أثناء مرحلة تدريبه على البيانات النصية المتوفرة على الإنترنت في هذا المقال لتوليد أسئلة للمستخدم عن موضوع معين يطلبه المستخدم، ثم عرض الأجوبة التي يُدخلها المستخدم على النموذج لتحديد مستوى فهمه لذلك الموضوع وحتى تصحيح أجوبته إن كانت خاطئة، سنلاحظ أيضًا الطيف الواسع من الاحتمالات الممكنة عند التعامل مع ذلك النموذج، من توليد المحتوى إلى طلب البيانات بشكل وتنسيق محدد يسهل تكامله مع التطبيق. تجهيز المشروع وإعداد الصفحات نبدأ بإنشاء مجلد جديد للمشروع وننفذ الأمر التالي بداخله والذي سيُعرّف المشروع كحزمة وتوليد ملف توصيف المشروع package.json والذي يمكن تعريف بعض الأوامر الخاصة بالمشروع ضمنه وتحديد الاعتماديات التي يريدها: npm init -y نثبت الاعتماديات التي نحتاجها لبناء المشروع وهي مكتبة express لإنشاء خادم الويب للتطبيق والمكتبة ejs لبناء قوالب HTML لصفحات المشروع، ومكتبة openai للتعامل مع الواجهة البرمجية API لنموذج ChatGPT ومكتبة dotenv والتي سنحتاجها لتحميل متغيرات البيئة من ملف env. للمشروع، وذلك بتنفيذ الأمر التالي: npm install express ejs openai dotenv لنبدأ ببناء خادم الويب وتجهيز الصفحات الأساسية للتطبيق وهي ثلاثة صفحات صفحة يدخل فيها المستخدم موضوع الاختبار، وصفحة لعرض الاختبار وإدخال الأجوبة، وصفحة عرض النتيجة، ونولد أولًا تطبيق جديد باستدعاء تابع مكتبة Express ونضبط إعداداته لاستخدام محرك القوالب ejs كمحرك توليد ملفات العرض وتخديم الملفات الثابتة العامة من المجلد public ضمن المشروع، واستقبال الدخل المرسل من نموذج HTML والتعامل معه، وذلك ضمن الملف bin/www ونضيف له ترويسة تحديد بيئة التشغيل لذلك الملف كالتالي: #!/usr/bin/env node const path = require("path"); const express = require("express"); // إنشاء تطبيق جديد const app = express(); // ضبط محرك توليد ملفات العرض app.set('view engine', 'ejs'); //إعداد تخديم الملفات العامة app.use(express.static(path.join(__dirname, '../public'))); // ضبط استقبال المدخلات app.use(express.urlencoded({ extended: true })) نُعرّف ضمن متغير التطبيق app ثلاث مسارات وهي المسار الرئيسي / بالطريقة GET لعرض الصفحة الرئيسية، والمسار test/ لعرض الاختبار، والمسار result/ لعرض نتيجة الاختبار، وضمن كل منها نٌصيّر ونعيد صفحة القالب المقابلة لذلك المسار باستخدام التابع render من كائن الطلب req المُمرر لتابع معالجة الطلب لكل مسار، ليكون تعريف المسارات كالتالي مع تمرير بعض البيانات الوهمية لاختبار الصفحات والتي سنستبدلها لاحقًا: // الصفحة الرئيسية app.get('/', (req, res) => { res.render('index'); }) // صفحة الاختبار app.post('/test', async (req, res) => { res.render('test', { questions: [ 'السؤال الأول', 'السؤال الثاني', ] }); }) // صفحة عرض النتيجة app.post('/result', async (req, res) => { res.render('result', { results: [ { question: 'السؤال الأول', answer: 'جواب السؤال الأول', correct: true, note: null, }, { question: 'السؤال الثاني', answer: 'جواب السؤال الثاني', correct: false, note: 'ملاحظة حول الإجابة الخاطئة', } ], }) }) ملفات قوالب صفحات العرض مكانها الافتراضي ضمن المجلد views، وبما أننا نستخدم محرك ejs يجب أن تنتهي جميع تلك الملفات باللاحقة ejs.، ونربط ضمنها ملف التنسيقات style.css والذي يمكننا إنشاءه في المسار public\style.css، والصفحات هي الصفحة الرئيسية views\index.ejs وتحوي حقل لإدخال موضوع الاختبار: <html lang="ar"> <head> <link rel="stylesheet" href="style.css" /> <title>اختبرني</title> </head> <body> <h1>?</h1> <form action="test" method="post"> <input type="text" name="subject" placeholder="أدخل موضوع الاختبار"/> <button type="submit">اختبرني</button> </form> </body> </html> والصفحة الثانية لعرض الأسئلة المقترحة، حيث نمرر لقالب العرض القيمة questions والتي تحوي الأسئلة كمصفوفة سلاسل نصية، نمر عليها ضمن القالب ونعرض لكل منها نص السؤال ثم حقل مخفي يحوي نص السؤال لإرساله مع حقل الجواب المُدخل من المستخدم، والبيانات ضمن النموذج المُرسل ستحوي على القيمة answers وهي مصفوفة من الكائنات الذي يُمثل كل منها السؤال question مع الجواب answer المُدخل من قبل المستخدم لتكون من الشكل التالي: { "answers": [ { "question": "...", "answer": ""}, … ] } نستعمل صيغة الأقواس المربعة [question][index]answers و [answer][index]answers كأسماء لتلك الحقول ليتولى express ترجمتها إلى الكائن answers وقيمته مصفوفة من كائنات يحوي كل منها الحقل question لنص السؤال والحقل answer للجواب المقابل لذلك السؤال، ليكون ملف قالب صفحة الأسئلة كالتالي: <html lang="ar"> <head> <link rel="stylesheet" href="style.css" /> <title>اختبرني | الاختبار</title> </head> <body> <form action="result" method="post"> <% questions.forEach((question, index) => { %> <p dir="auto"><%- question %></p> <input type="hidden" name="answers[<%= index %>][question]" value="<%- question %>" /> <input type="text" name="answers[<%= index %>][answer]" /> <% }) %> <button type="submit">عرض النتيجة</button> </form> </body> </html> والصفحة الأخيرة لعرض النتيجة، حيث سنمرر لقالب العرض القيمة results وهي مصفوفة من كائنات نتائج الأسئلة، يحوي كل منها على الحقل question لنص السؤال والحقل answer للجواب المدخل من قبل المستخدم سنضعه كقيمة لحقل input مع الخاصية readonly لمنع تعديله، والحقل المنطقي correct يعبر عما إذا كان الجواب صحيحًا أم لا، وأخيرًا الحقل note والذي سنعرضه في حال كان الجواب خاطئًا ويحوي على ملاحظة من قبل ChatGPT عن الإجابة الصحيحة، وأخيرًا رابط للصفحة الرئيسية لطلب اختبار جديد كالتالي: <html lang="ar"> <head> <link rel="stylesheet" href="style.css" /> <title>اختبرني | النتيجة</title> </head> <body> <% results.forEach((result, index) => { %> <div class="result"> <p dir="auto"><%- result.question %></p> <input type="text" value="<%= result.answer %>" readonly class="<% if(result.correct){ %> correct <% } else { %> wrong <% } %>"/> <% if(!result.correct) { %> <div class="wrong"><%= result.note %></div> <% } %> </div> <% }) %> <a href="/">اختبار جديد</a> </body> </html> وقبل أن نعاين الصفحات يجب أن نضيف تعليمة تشغيل خادم الويب بنهاية الملف bin/www على منفذ محدد نستخرجه من متغيرات البيئة كالتالي: // رقم المنفذ من متغيرات البيئة const port = process.env.PORT; // تشغيل خادم الويب app.listen(port, () => console.log(`Listening on http://localhost:${port}`)) ولتحميل متغيرات البيئة من ملف env. نضعه ضمن مجلد المشروع مباشرةً نستخدم المكتبة dotenv بإضافة التعليمة التالية في بداية نفس الملف لتحميل القيم من env. وتعيينها كمتغيرات بيئة مباشرةً كالتالي: #!/usr/bin/env node // تحميل متغيرات البيئة require('dotenv').config() ... وننشئ الملف env. ونعرف ضمنه القيمة PORT بأي رقم منفذ نريده وليكن 3000: PORT=3000 ونُعرّف ضمن ملف تعريف الحزمة package.json النص البرمجي start لتشغيل الخادم ضمن القيمة scripts كالتالي: "scripts": { "start": "node bin/www" }, لتنسيق جميع الصفحات يمكن التعديل على الملف public/style.css ولن نعرض محتواه اختصارًا، وبعد التعديل عليه لتنسيق الصفحات يمكن معاينتها بتشغيل الخادم بتنفيذ التعليمة التالية بطرفية ضمن مجلد المشروع: npm start نزور الصفحة الرئيسية على الرابط المعروض بعد تشغيل الخادم: ندخل أي قيمة ضمن الصفحة السابقة لنعاين صفحة عرض الأسئلة لنشاهد التالي: ونضغط عرض النتيجة لنعاين صفحة عرض النتيجة النهائية كالتالي: بعد أن جهزنا ملفات المشروع بالكامل والصفحات المطلوبة أصبح جاهزًا الآن لربطه مع ChatGPT لتوليد المحتوى المطلوب وعرضه وهو ما سنبدأ به في الفقرات التالية. تحضير الربط مع ChatGPT قبل أن نتمكن من التعامل مع الواجهة البرمجية لنموذج ChatGPT علينا التحضير بعدة خطوات، أولًا عبر تسجيل حساب مدفوع ضمن موقع شركة OpenAI، ومنه توليد مفتاح واجهة برمجية جديد ثم نسخه إلى ملف env. ضمن المشروع وتحديده كقيمة للمفتاح OPENAI_API_KEY كالتالي: ... OPENAI_API_KEY=<قيمة المفتاح> وضمن الملف نستخدم المكتبة openai التي ثبتناها في بداية المقال ونُنشئ كائن جديد من الصنف الذي توفره OpenAIApi ونمرر له الإعدادات المناسبة مع قيمة المفتاح السابق من متغيرات البيئة في بداية الملف كالتالي: ... const { OpenAIApi, Configuration } = require("openai"); const openai = new OpenAIApi( new Configuration({ apiKey: process.env.OPENAI_API_KEY }) ) يمكننا الآن استخدام الثابت openai للتعامل مع الواجهات البرمجية التي توفرها OpenAI وتحديدًا نموذج ChatGPT وهو ما سنبدأ بالتعامل معه في الفقرة التالية. توليد أسئلة الاختبار كل ما علينا الآن هو تعديل توابع معالجة الطلبات المرسلة والطلب من نموذج ChatGPT توليد بعض الأسئلة عن الموضوع الذي أرسله المستخدم من الصفحة الرئيسية إلى المسار test/ باستخراج قيمة موضوع الاختبار ضمن تابع معالجة الطلب كالتالي: app.post('/test', async (req, res) => { // استخراج موضوع الاختبار const { subject } = req.body ... }) نحدد التعليمة التي سنرسلها إلى النموذج، وهي أهم خطوة في تلك العملية، فالتعليمة الواضحة والجيدة ينتج عنها النتيجة التي نريدها وفي حالتنا سنخبر النموذج بتوليد بعض الأسئلة عن الموضوع السابق وشكل التعليمة سيكون كالتالي: نلاحظ كيف حددنا شكل الخرج الذي نريده وهو أن يكتب كل سؤال ضمن سطر لنتمكن من استخراجه، ويمكننا تحديد شكل الخرج بطريقة متقدمة سنتعرف عليها لاحقًا أما حاليًا يمكننا فصل الجواب الناتج من النموذج إلى الأسطر المكونة له واعتبار كل سطر هو سؤال، ونبني التعليمة السابقة وندرج ضمنها قيمة الموضوع المرسل ونستخدم الثابت openai لإرسال رسالة إلى النموذج gpt-3.5-turbo وهو النسخة المتاحة دون اشتراك شهري وقت كتابة هذا المقال، حيث تحوي الرسالة على التعليمة السابقة، ثم نستخرج من الجواب قيمة الرسالة المُرسلة من النموذج ليصبح تابع معالجة طلب توليد أسئلة عن موضوع معين كالتالي: app.post('/test', async (req, res) => { // استخراج موضوع الاختبار const { subject } = req.body // بناء التعليمة const prompt = `أكتب أربعة أسئلة يمكن الاجابة عليها بجواب قصير لاختبار مستوايي في ${subject}، الأسئلة كل سؤال بسطر:`; // إرسال طلب إلى النموذج const response = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', messages: [ { role: 'user', content: prompt } ] }) // استخراج الجواب const message = response.data.choices[0].message.content // استخراج الأسئلة المولدة من الجواب const questions = message.split('\n') // تمرير الأسئلة المقترحة لصفحة الاختبار res.render('test', { questions }); }) وفي الفقرة التالية سنستقبل الأجوبة من المستخدم ونُقيمها باستخدام ChatGPT مجددًا ونعرض النتيجة للمستخدم. إرسال الإجابات للتقييم بعد إدخال الإجابات في صفحة الاختبار وإرسالها إلى مسار عرض النتيجة result/ يمكننا ضمن ذلك المسار استقبال الإجابات من الكائن answers من جسم الطلب: app.post('/result', async (req, res) => { // استخراج الإجابات const { answers } = req.body ... }) قيمة ذلك المتغير ستكون مصفوفة من كائنات يحوي كل منها على نص السؤال ضمن الحقل question والجواب المُدخل من المستخدم answer، والتي سنضمنها ضمن التعليمة المُرسلة إلى ChatGPT لتقييمها، وبما أن التقييمات لكل سؤال نريدها أن تحوي معلومات عن صحة الجواب وملاحظة في حال كان خاطئًا، فيمكننا الطلب من ChatGPT إعادة الجواب بصيغة JSON ليسهل علينا استخراج المعلومات منه وعرضها للمستخدم ضمن التطبيق، وذلك بإعطاء مثال عن شكل الخرج في نهاية التعليمة ليكون شكل التعليمة النهائي كالتالي: قيم الإجابات السابقة مع الشرح بصيغة JSON التالية: [{correct: false, note:"تعليل أو ملاحظة حول الإجابة...."}، ...] نبني تلك التعليمة من الأجوبة المرسلة كالتالي: const { answers } = req.body // الأسئلة مع الأجوبة const questionsAndAnswers = answers .map(({ question, answer }) => `السؤال: ${question}\nالجواب: ${answer}`) .join('\n\n') // بناء التعليمة const prompt = `${questionsAndAnswers}\n\nقيم الإجابات السابقة مع الشرح بصيغة JSON التالية: [{correct: false, note:"تعليل أو ملاحظة حول الإجابة...."}، ...]`; ثم نرسلها إلى ChatGPT كما فعلنا سابقًا والفرق هذه المرة في معالجة الجواب حيث سنترجمه من صيغة JSON إلى مصفوفة في جافاسكريبت باستخدام التابع JSON.parse // إرسال التعليمة const response = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', messages: [ { role: 'user', content: prompt } ] }) // استخراج الجواب const message = response.data.choices[0].message?.content // معالجة الجواب const results = JSON.parse(message) والآن نعرضها ضمن قالب العرض result.ejs باستخدام التابع renderمع تمرير نص السؤال والجواب المدخل من المستخدم وتقييم الجواب والملاحظة حوله كالتالي: // عرض النتيجة res.render('result', { results: results.map((result, index) => ({ question: answers[index].question, answer: answers[index].answer, note: result.note, correct: result.correct, })), }) وبذلك يصبح التطبيق جاهزًا للاختبار سنستعرضه ضمن الفقرة التالية. اختبار التطبيق لنحاول الإجابة على بعض الأسئلة المتعلقة بالبرمجة في نود لاختبار المستوى: نلاحظ اقتراح أسئلة متنوعة عن ذلك الموضوع، لنحاول الإجابة عليها باللغة العربية وترك الجواب الأخير خاطئًا: عاين ChatGPT تلك الأجوبة وحدد الصحيح والخاطئ منها بفهم النص المكتوب وربطه بسؤاله بدقة وحدد الجواب الأخير بأنه خاطئ وأعطى ملاحظة عن سبب الخطأ: خاتمة كما رأينا سهولة التعامل مع ChatGPT والاحتمالات الواسعة الممكنة ضمنه، من توليد محتوى محدد إلى قراء وفهم مدخلات المستخدم إلى إمكانية تحديد شكل الخرج المطلوب لتسهيل التعامل معه برمجيًا، ويمكن التطوير على التطبيق الذي طورناه في هذا المقال بعدة أفكار، فمثلًا يمكن تحديد عدد الأسئلة من قبل المستخدم والطلب من ChatGPT ذلك العدد من الأسئلة، أو يمكن حفظ سجل الأسئلة المجاب عنها من قبل المستخدم وإرسالها مع طلب توليد الأسئلة الجديدة والطلب منه أن تكون الأسئلة مختلفة في مواضيعها عن السابقة ما يغني تجربة المستخدم، أو إذا كان موضوع الأسئلة محددًا يمكن توليد العديد من أسئلة اختيار من متعدد عن ذلك الموضوع دفعة واحدة مع تحديد الإجابة الصحيحة لكل سؤال ثم تخزينها ضمن قاعدة بيانات أسئلة كبيرة وتُعرض للمستخدم عشوائيًا كي يتمرن عليها ونحدد مستواه وبذلك لا داعي للاتصال مع النموذج خلال مرحلة الإنتاج ونستفيد منه لتوليد محتوى الأسئلة فقط. اقرأ أيضًا برمجة تطبيق 'لخصلي' لتلخيص المقالات باستخدام ChatGPT ولارافل تطوير تطبيق 'وصفة' لاقتراح الوجبات باستخدام ChatGPT و DALL-E في PHP
-
يسعى مجال الذكاء الاصطناعي إلى محاكاة ذكاء البشر في قدرتهم على تحليل المعطيات الحسية كالصورة والأصوات واستنتاج المعلومات المفيدة منها، وبينما يُمثل البشر تلك المعطيات بيولوجيًا طور علماء الرياضيات والحاسوب طرقًا لتمثيل ومعالجة تلك المعطيات رقميًا لنتمكن من معالجتها بخوارزميات رياضية ضمن الحاسوب، حيث يمكن تدريب نماذج الذكاء الاصطناعي على البيانات لتتمكن محاكاة ذكاء البشر في مجال محدد، أحد أنواع تلك النماذج هي نماذج اللغة الكبيرة LLM's (أو Large Language Models) وهي نماذج دُربت على كميات كبيرة من النصوص فامتلكت القدرة على توليد نصوص مشابهة لها وبل حتى فهم نصوص جديدة لم تشاهدها ضمن بيانات التدريب من قبل. من نماذج اللغة الكبيرة النموذج ChatGPT من شركة OpenAI والذي دُرّب على كمية كبيرة من النصوص الموجودة على شبكة الانترنت وطوّر ليستجيب إلى الأوامر المرسلة له من قبل المستخدم، مما يجعل هذا النموذج قادرًا على محاكاة تلك النصوص وتحديد النقاط الرئيسية فيها والرد عليها بردود كما لو أنها مكتوبة من قبل إنسان، لذا يمكن الاستفادة من مرونة التعامل معه وفهمه للأوامر باللغة الطبيعية في العديد من التطبيقات العملية، كتوليد المحتوى أو التعديل على نص معين أو التلخيص واستخراج النقاط الرئيسية من نص ما والكثير من الأفكار الأخرى، أي يمكن اعتبار تلك النماذج كمساعد ذكي يمكن طلب أي مهمة منه كما لو كنا نطلبها من شخص حقيقي، وأهم ما في تلك النماذج هو سهولة برمجتها نسبيًا، فلم نعد بحاجة لتعلم لغة برمجة مخصصة بل يمكننا التعامل معها باللغة الطبيعية وبعدة لغات أيضًا منها اللغة العربية كما سنرى لاحقًا. وفي هذا المقال سنستفيد من قدرة ذلك النموذج على فهم النصوص وإعادة صياغتها وتلخيصها في تلخيص مقالات من الإنترنت، حيث سنبني تطبيق ويب باستخدام لارافل Laravel لجلب محتوى المقال الذي يرغب المستخدم في تلخيصه، ثم الطلب من نموذج ChatGPT تلخيص ذلك المقال للمستخدم، ولمتابعة هذا المقال يجب أن تملك خبرة أساسية في لغة PHP عمومًا وإطار لارافل خصوصًا. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن إنشاء مشروع لارافل جديد وبناء الواجهات الأمامية يتألف مشروعنا بشكل عام من صفحتين رئيسيتين الأولى يدخل فيها المستخدم رابط المقال الذي يريد تلخيصه، والثانية يظهر له فيها الملخص لذلك المقال بعد أن يجلب التطبيق صفحة المقال من على الأنترنت ويستخرج محتوى المقال ويرسلها إلى نموذج ChatGPT لتلخيصها كما سنرى بالتفصيل لاحقًا، ولنبدأ أولًا بإنشاء مشروع وليكن بالاسم summarizer عبر تنفيذ الأمر التالي ضمن الطرفية والذي سيُنشئ مجلد جديد باسم المشروع يحوي هيكلية الملفات الأساسية لمشروع لارافل جديد: composer create-project laravel/laravel summarizer نحتاج لهذا المشروع صفحتين، الصفحة الأولى لإدخال رابط المقال المُراد تلخيصه وتحوي على نموذج form بسيط يرسل القيم بداخله بالطريقة POST إلى المسار summary/ والذي سنُعرفه لاحقًا، ويحوي النموذج على حقل لإدخال الرابط بالاسم url وزر الإرسال ولا ننسى تضمين قيمة الحقل csrf ضمن النموذج، وننشئ للصفحة ملف جديد بصيغة قالب blade جديدة ضمن المسار التالي resources\views\index.blade.php يحوي التالي: <head> <title>لخصلي</title> <link rel="stylesheet" href="style.css"> </head> <body> <h1>✍?</h1> <form action="/summary" method="POST"> @csrf <input type="url" name="url" placeholder="أدخل رابط المقال"> <button type="submit">لخصلي</button> </form> </body> والصفحة الثانية هي لعرض نتيجة التلخيص، تحوي على الملخص ورابط للمقال الأصل في حال أراد المستخدم قراءته ورابط للرجوع للصفحة الرئيسية لطلب تلخيص جديد، ونُنشئ ملف قالب تلك الصفحة ضمن المسار resources\views\summary.blade.php ويحوي التالي: <head> <title>لخصلي</title> <link rel="stylesheet" href="style.css"> </head> <body> <h2>ملخص المقال</h2> <p>{{ $summary }}</p> <footer> <a href="{{$url}}" target="_blank">قراءة المقال</a> <a href="/">تلخيص مقال جديد</a> </footer> </body> نلاحظ ربط ملف تنسيقات CSS بالاسم style.css في كلا الصفحتين لتنسيق محتواهما، ونُنشئ ذلك الملف ضمن المسار public\style.css ويحوي التنسيق التالي: body { direction: rtl; display: flex; flex-direction: column; max-width: 60ch; justify-items: center; align-items: center; font-size: x-large; margin: 0 auto; padding: 20vh 10vw; background-color: #f9f9f9; color: #121212; font-family: system-ui; } p { line-height: 2; text-align: center; border: .25rem solid; padding: 3rem; white-space: pre-line; } a { padding: 1rem; font-size: inherit; margin: 0 .5rem; color: darkblue; } footer { margin-top: 2rem; } form { display: flex; flex-direction: column; gap: 2rem; text-align: center; } button { background-color: #121212; color: #f9f9f9; border: none; padding: 1rem 2rem; font-size: inherit; cursor: pointer; font-family: inherit; } input { padding: 1rem 2rem; font-size: inherit; font-family: inherit; text-align: center; border: .2rem solid; } نعرف ضمن ملف التوجيه routes\web.php مسارين الأول لعرض صفحة الإدخال نعيد فيها قالب الصفحة الأولى، والمسار الثاني summary/ لاستقبال طلبات HTTP من نوع POST نستخرج ضمنه قيمة الرابط url المُدخل من قبل المستخدم ويلخص ذلك المقال ثم يعرض النتيجة ضمن قالب الصفحة الثانية ليكون محتوى الملف كالتالي: <?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; // صفحة الإدخال Route::view('/', 'index'); // صفحة عرض التلخيص Route::post('/summary', function (Request $request) { $url = $request->input('url'); return view('summary', [ 'url' => $url, 'summary' => 'ملخص المقال هنا' ]); }); والآن يمكننا استعراض الصفحتين بتشغيل خادم التطوير أولًا باستخدام الأمر التالي: php artisan serve واستعراض الصفحة الرئيسية: وإدخال أي رابط ضمن تلك الصفحة وطلب تلخيصه لنستعرض صفحة النتيجة: بعد أن جهزنا المشروع الجديد والصفحات وعملية إرسال طلب تلخيص جديد سنبدأ في الفقرات التالية تضمين عملية جلب محتوى المقال المطلوب ثم تلخيصه باستخدام ChatGPT. جلب محتوى المقال نبدأ بإنشاء صنف جديد بالاسم Article ضمن المسار app\Article.php والذي سيكون مسؤولًا عن جلب محتوى المقال عن طريق الرابط له، ويحوي على التابع fromURL والذي يقبل رابط المقال كمعامل أول ويعيد نص المقال كسلسلة نصية، ويُنفذ ذلك باستخدام الصنف المساعد HTTP الذي يتيحه لارافل لإرسال طلب من نوع GET لجلب محتوى صفحة المقال كاملةً، ثم الاستعانة بالصنف DOMDocument لتحليل محتوى HTML من تلك الصفحة واستخراج المحتوى النصي لعناصر الفقرات ذات الوسم p والتي تحوي عادةً المحتوى النصي للمقال، ونجمع محتوى تلك الفقرات معًا في سلسلة نصية لتكون المحتوى النصي للمقال كاملًا ضمن الصفحة كالتالي: <?php namespace App; use DOMDocument; use Illuminate\Support\Facades\Http; class Article { public function fromURL($url): string { // جلب محتوى الصفحة $html = Http::withHeaders(['User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36']) ->get($url) ->body(); // استخراج الفقرات $document = new DOMDocument(); @$document->loadHTML($html); $paragraphs = $document->getElementsByTagName('p'); // جمع محتوى الفقرات معًا $article = ''; foreach ($paragraphs as $paragraph) { $article = trim($article . "\n\n" . $paragraph->textContent); } return $article; } } أضفنا للطلب المُرسل الترويسة User-Agent والتي تتطلبها بعض المواقع لإرسال محتوى الصفحة، ونلاحظ أيضًا إضافة العلامة @ قبل تحميل محتوى الصفحة باستخدام التابع loadHTML لتجنب بعض الأخطاء التي قد تحدث عند محاولة تفسير محتوى الصفحة، ولاستخدام ذلك الصنف يمكننا تمريره كمعامل لتابع معالجة مسار صفحة النتيجة ليُنشئ لارافل تلقائيًا كائنًا جديدًا من ذلك الصنف ويمرره لتابع المسار لنتمكن من استخدامه لجلب محتوى المقال من الرابط المُرسل كالتالي: use App\Article; ... // صفحة عرض التلخيص Route::post('/summary', function (Request $request, Article $article) { $url = $request->input('url'); // جلب محتوى المقال $content = $article->fromURL($url); return view('summary', [ 'url' => $url, 'summary' => 'ملخص المقال هنا' ]); }); وفي الفقرة التالية سنرسل ذلك المحتوى إلى ChatGPT لتلخيصه ثم عرضه للمستخدم. تلخيص محتوى المقال باستخدام ChatGPT بعد أن استخرجنا محتوى المقال كاملًا يمكننا إرساله إلى ChatGPT محاطًا بتعليمة نخبر بها النموذج بأننا نريد ملخص قصير عن ذلك النص، ولكن نماذج اللغة الكبيرة مثل ChatGPT تملك حد أقصى من طول المحتوى المُرسل إليها وهو 4097 رمز (أو token) بحسب التوثيق الرسمي للنموذج، والرمز هو قطعة من النص يحدد طولها بكلمة أو عدة أحرف أو عدة كلمات أحيانًا، أي في حال كان المقال طويل وعدد الرموز المكونة له أكبر من الحد الأقصى لن نتمكن من إرساله دفعة واحدة، ويمكن الاطلاع من خلال أداة توفرها الشركة على حساب تلك الرموز من أي نص. ويمكن حل المشكلة الحد الأقصى للرموز بعدة طرق والطريقة التي سنستخدمها هي تقسيم نص المقال إلى عدة مقاطع أقصر، ثم تلخيص كل مقطع منها على حدى وجمعها معًا للحصول على ملخص المقال كاملًا، وفي حال كان ذلك الملخص أطول مما نرغب بعرضه للمستخدم نعيد تلخيص الملخص نفسه مجددًا للحصول على ملخص أقصر، ونكرر ذلك حتى الحصول على ملخص بطول مناسب للمقال كاملًا، وانتبه إلى أن هذه الطريقة اعتمدناها فقط لأغراض تعليمية وقد لا تعطي نتائج جيدة لذا ابحث عن طريقة أفضل لحل هذه المشكلة. نكمل العمل بإنشاء صنف جديد بالاسم Summary ضمن المسار app\Summary.php سيحوي على عدة لتقسيم وتلخيص النصوص سنشرحها تباعًا: <?php namespace App; class Summary { ... } التابع الرئيسي لتلخيص المقال اسمه toWords وهو يستقبل النص المراد تلخيصه وعدد يعبر عن عدد الكلمات الأقصى للملخص النهائي لذلك النص، بحيث يكون ذلك التابع مسؤولًا عن تلخيص النص باستخدام التابع summarize الذي سنعرفه لاحقًا، ثم التحقق من عدد كلمات الملخص وتكرار عملية التلخيص إن لزم الأمر إلى أن نحصل على ملخص نهائي بالطول المناسب: class Summary { public function toWords(int $at_most, string $text) { // تلخيص أولي للنص $summary = $this->summarize($text); // تكرار عملية التلخيص حسب الطول المطلوب while (count(explode(" ", $summary)) > $at_most) $summary = $this->summarize($summary); return $summary; } public function summarize(string $text): string { ... } } ونستدعي ذلك التابع ضمن تابع معالجة بعد تعريفه ضمن المعاملات في ملف التوجيه routes/web.php لتلخيص محتوى المقال بعد استخراجه إلى عدد كلمات مناسب وليكن 150 كلمة وتمرير الملخص إلى قالب الصفحة لعرضه للمستخدم كالتالي: // صفحة عرض التلخيص Route::post('/summary', function (Request $request, Article $article, Summary $summarizer) { $url = $request->input('url'); // جلب محتوى المقال $content = $article->fromURL($url); // تلخيص المقال $summary = $summarizer->toWords(100, $content); return view('summary', [ 'url' => $url, 'summary' => $summary ]); }); التابع summarize ضمن الصنف Summary مهمته تجزئة النص إلى فقرات بطول مناسب لإرسالها إلى ChatGPT للتلخيص، حيث نبدأ بتجزئة النص إلى فقراته التي يفصل بينها سطر فارغ، ثم تجميعها إلى مجموعات ولتكن مؤلفة من 8 فقرات معًا، بعدها نطلب تلخيص كل مجموعة على حدى، وبما طلب تلخيص كل مجموعة لا يتعلق بالأخرى فيمكننا إرسال طلبات التلخيص لتلك المجموعات معًا على التوازي لاختصار وقت التلخيص الكلي، فلا داعي في طريقتنا لانتظار تلخيص أول فقرة حتى نلخص التي تليها، وننفذ ذلك باستخدام التابع pool من الصنف المساعد في لارافل HTTP والذي نمرر له تابعًا يقبل معامل من النوع Pool والذي يعبر عن مجموعة من طلبات HTTP ستُرسل معًا على التوازي، توليد الطلب الواحد سنوكله لتابع منفصل بالاسم summaryRequest، وبعد انتهاء كل الطلبات نستخرج منها الملخصات من مفتاح JSON في المسار choices.0.message.content باستخدام التابع json من كل طلب منها، ثم نضمها معًا مجددًا لتشكل الملخص لكل الفقرات المرسلة: class Summary { ... public function summarize(string $text) { // الفاصل بين الفقرات $seperator = "\n\n"; // فصل النص إلى فقرات بطول مناسب $paragraphs = collect(explode($seperator, $text)) ->chunk(8) ->map(fn ($chunks) => $chunks->join($seperator)); // إرسال طلبات التلخيص على التوازي $responses = Http::pool(fn (Pool $pool) => $paragraphs->map( fn (string $paragraph) => $this->summaryRequest($pool, $paragraph) )); // استخراج تجميع الملخصات معًا return collect($responses) ->map(fn ($response) => $response->json('choices.0.message.content')) ->join($seperator); } protected function summaryRequest(Pool $pool, string $text) { ... } } ضمن التابع summaryRequest سنرسل الطلب باستخدام pool$ إلى ChatGPT لتلخيص محتوى الفقرة text$ المٌمررة له، وإرسال أي طلب للواجهة البرمجية الخاصة به تحتاج منا أولًا الحصول على مفتاح استيثاق يمكن توليده بعد إنشاء حساب جديد ضمن موقع الشركة المالكة للنموذج OpenAI وإكمال إعدادات الحساب وتعيين وسيلة الدفع ضمنه، وبعد توليد المفتاح نضعه ضمن ملف متغيرات البيئة env. ضمن مفتاح بالاسم OPENAI_API_KEY ضمن ملفات المشروع لنتمكن من اختبار إرسال الطلبات محليًا كالتالي: ... OPENAI_API_KEY=<مفتاح الاستيثاق> وبحسب صفحة التوثيق الخاصة بتلك الواجهة البرمجية فيجب أن نضع ذلك المفتاح ضمن قيمة الترويسة Authorization يسبقها الكلمة Bearer ويفصل بينهما مسافة، ويمكننا استخراج قيمة المفتاح السابق باستخدام التابع env من لارافل، ويُرسل طلب HTTP بالنوع POST إلى نموذج المحادثة ChatGPT إلى مسار الواجهة البرمجية https://api.openai.com/v1/chat/completions، وضمن جسم الطلب نحدد قيمتين، الأولى هي اسم النموذج ضمن المفتاح model في حالتنا هو gpt-3.5-turbo، والقيمة الثانية هي messages تحوي تاريخ الرسائل الواقعة بين المستخدم والنموذج سابقًا، وفي حالتنا نحتاج رسالة مستخدم واحدة تحوي تعليمات للنموذج بتلخيص الفقرة ضمن تلك الرسالة وصيغتها كالتالي: سيفهم النموذج التعليمة السابقة ويرد برسالة تحوي تلخيص للفقرة المذكورة، ليكون بذلك تابع تلخيص الفقرة summaryRequest كالتالي: protected function summaryRequest(Pool $pool, string $text) { return $pool ->withHeaders(['Authorization' => "Bearer " . env('OPENAI_API_KEY')]) ->post('https://api.openai.com/v1/chat/completions', [ 'model' => 'gpt-3.5-turbo', 'messages' => [ ['role' => 'user', 'content' => implode("\n", [ "لخص الفقرة التالية من المقال تلخيص دقيق:", $text, "التلخيص:" ])] ], ]); } بذلك يكون تضمين صنف التلخيص Summary جاهزًا سنختبره في الفقرة التالية ونرى نتيجة تلخيصه. اختبار التطبيق لنختبر التطبيق بتلخيص مقال من موقع أكاديمية حسوب عن الذكاء الاصطناعي بإدخال رابطه ضمن صفحة الإدخال وطلب التلخيص: نلاحظ أن الملخص يحوي ذكر لمعظم الأفكار التي يغطيها المقال ما يعطي صورة عامة عنه، ولنختبره مجددًا بتلخيص جديد من أكاديمية حسوب عن تعلم البرمجة: يمكن التطوير على التطبيق من عدة نواحي، فمثلًا يمكن أن نوفر للمستخدم خيارات لإدخال محتوى المقال عن طريق رابط مثلًا أو نسخ المحتوى مباشرةً، أو أن نسمح للمستخدم بتحديد طول الملخص الذي يرغب به، أو الطلب من النموذج كتابة الملخص على شكل نقاط رئيسية، أو تحديد لغة النتيجة قبل عرضها للمستخدم، وهناك العديد من الأفكار المفيدة التي يمكن تطبيقها بالاستفادة من نماذج اللغة كنموذج ChatGPT وميزاته بفهم اللغة الطبيعية بلغات متعددة. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن اقرأ أيضًا تطوير تطبيق 'وصفة' لاقتراح الوجبات باستخدام ChatGPT و DALL-E في PHP تصنيف الصور والتعرف على الوجه في مجال الذكاء الاصطناعي دليلك الشامل إلى: برمجة التطبيقات دليلك الشامل إلى: لغة PHP
-
كثيرًا ما نحتار في اختيار وجبة طعام اليوم لتحضيرها، أو قد تتوفر لدينا بعض المكونات التي نريد تحضير وصفة ما منها ونحتار في اختيار الوصفة المناسبة، فيمكننا البحث عبر الإنترنت عن وصفات معينة واختيار الأنسب منها، لكن ماذا لو استطاع الذكاء الاصطناعي اختيار الوجبة لنا ووصف طريقة تحضيرها بل وحتى تخيل شكل الطبق النهائي، ففي هذا المقال سنستفيد من قدرات الذكاء الاصطناعي في فهم اللغة الطبيعية وقدرته على توليد المحتوى النصي والبصري لبناء تطبيق نُخبره بالمكونات الموجودة لدينا ومن أي مطبخ نُفضل أن تكون الوصفة وسيقترح لنا وصفة مناسبة مع شرح طريقة تحضيرها، حيث سنستخدم لتطويره لغة PHP وبعض نماذج الذكاء الاصطناعي التي توفرها شركة OpenAI. نماذج الذكاء الاصطناعي واستخداماتها تتميز نماذج الذكاء الاصطناعي بسرعة معالجة وتحليل المعلومات والطيف الواسع من القدرات الكامنة ضمنها، حيث يمكن للنموذج بعد تدريبه تكوين فهمًا جيدًا عن البيانات وبالتالي يستطيع توليد أو التعامل مع بيانات جديدة لم يراها سابقًا، فمثلًا نموذج GPT (اختصارًا لعبارة المحول التوليدي مسبق التدريب Generative Pre-Trained Transformer) المطور من قبل شركة OpenAI دُرّب على كمية كبيرة من البيانات النصية فكون فهم جيد عن اللغات البشرية بدءًا من صياغتها إلى تراكيب الجمل وصولًا إلى المعنى وراء النص، فأصبح بإمكانه إكمال كتابة النصوص كما لو كان من كتبها إنسان وهي وظيفته الأساسية، ونموذج DALL-E المطور من قبل نفس الشركة تم تدريبه على كمية كبيرة من الصور وتوصيفاتها فتكون ضمنه فهم وربط بين النصوص ومحتوى الصور، وبذلك أصبح قادرًا على توليد صور أقرب ما تكون إلى الطبيعية بناءً على توصيف نصي لها. في هذا المقال سنحاول حل مشكلة الحيرة في اختيار وجبة الطعام عبر تطوير تطبيق لاقتراح الوصفات باستخدام لغة البرمجة PHP، وسنستفيد من النموذج ChatGPT في اقتراح الوصفة على المستخدم بحسب ما يريد وشرح طريقة تحضيرها، ومن النموذج DALL-E لتوليد صورة تخيلية لطبق من تلك الوصفة، أي سيكون محتوى التطبيق مولّد بالكامل من قبل نماذج الذكاء الاصطناعي. دورة تطوير تطبيقات الويب باستخدام لغة PHP احترف تطوير النظم الخلفية وتطبيقات الويب من الألف إلى الياء دون الحاجة لخبرة برمجية مسبقة اشترك الآن تحضير ملفات المشروع وتطوير واجهة المستخدم سنحتاج بدايةً لبيئة PHP مثبتة وتعمل على نظام التشغيل، ويمكن تنزيل النسخة المناسبة من الموقع الرسمي لها ثم تثبيته على الجهاز، وللتأكد من صحة التثبيت يمكن تنفيذ الأمر التالي ضمن سطر الأوامر للتحقق من رقم النسخة المثبتة: php -v سيظهر معلومات عن رقم النسخة، ويفضل استخدام النسخة 7.4 أو أعلى، ونبدأ بإنشاء ملف جديد سيحتوي على ملفات المشروع وليكن بالاسم recipe-php، وننشئ ضمنه الملفات التالية: index.php ملف الصفحة الرئيسية suggestion.php ملف صفحة عرض النتيجة style.css ملف تنسيقات CSS لتكون بنية المجلد والملفات ضمنه كالتالي: recipe-php/ ├─ index.php ├─ suggestion.php ├─ style.css ملاحظة: تحتاج إلى أن تملك خبرة أساسية بلغة PHP، وإن كنت جديدًا عليها، فارجع إلى مقال تمهيد إلى لغة PHP ومقال الدليل السريع إلى لغة البرمجة PHP للتعرف على أساسيات اللغة. نبدأ ببناء صفحة الموقع الرئيسية ضمن الملف index.php، وفيها سنطلب من المستخدم إدخال معلومات حول الوصفة، كاختيار المطبخ الذي تنتمي إليه وكتابة بعض المكونات التي قد تكون لدى مستخدم التطبيق ويريد تحضير وجبة طعامه منها، ونبدأ بربط ملف التنسيقات style.css في بداية الملف باستخدام العنصر link، ثم نموذجًا باستخدام العنصر form سيرسل محتوياته إلى ملف صفحة النتيجة suggestion.php بالطريقة POST، ويحوي على حقل اسم المطبخ للوجبة المقترحة نضع فيها خيارات مسبقة مثل (سعودي - سوري - مغربي - يمني - مصري)، ثم حقل نصي اختياري يدخل فيه المستخدم المكونات التي لديه وز لإرسال الطلب، ليصبح ملف الصفحة كالتالي: <!-- index.php --> <head> <!-- استيراد ملف التنسيقات --> <link rel="stylesheet" href="style.css"> <!-- عنوان الصفحة --> <title>تطبيق وصفة</title> </head> <div class="logo">???</div> <form action="suggestion.php" method="post" dir="rtl"> <!-- اختيار المطبخ --> <fieldset> <legend>المطبخ</legend> <input type="radio" name="cuisine" id="cuisine_SA" value="سعودي" required> <label for="cuisine_SA">?? سعودي</label> <input type="radio" name="cuisine" id="cuisine_SY" value="سوري" required> <label for="cuisine_SY">?? سوري</label> <input type="radio" name="cuisine" id="cuisine_MA" value="مغربي" required> <label for="cuisine_MA">?? مغربي</label> <input type="radio" name="cuisine" id="cuisine_YE" value="يمني" required> <label for="cuisine_YE">?? يمني</label> <input type="radio" name="cuisine" id="cuisine_EG" value="مصري" required> <label for="cuisine_EG">?? مصري</label> </fieldset> <!-- مكونات الوصفة --> <div> <label for="ingredients">المكونات</label> <input type="text" name="ingredients" id="ingredients" placeholder="هل لديك مكونات محددة؟ مثال: رز بصل ..."> </div> <!-- إرسال المدخلات --> <button type="submit">اقترح وصفة ?</button> </form> نتوجه الآن لملف عرض النتيجة suggestion.php ونحضر فيه قالب عرض النتيجة، المكون من اسم الوصفة المقترحة وصورة لها ووصف سيحوي على المكونات وطريقة التحضير، وفي النهاية زر للرجوع للصفحة الرئيسية في حال رغب المستخدم اقتراح وصفة جديدة، ولا ننسى ربط ملف التنسيقات style.css في بداية الصفحة كما فعلنا سابقًا، ونحضر بعض المتغيرات الفارغة مبدأيًا سنضع فيها محتويات الصفحة بعد أن تولدها لنا نماذج الذكاء الاصطناعي في الفقرة التالية، ليصبح ملف صفحة النتيجة كالتالي: <!-- suggestion.php --> <?php $name = ''; // اسم الوصفة $recipe = ''; // المكونات وطريقة التحضير $image_url = ''; // رابط صورة الوصفة ?> <head> <!-- استيراد ملف التنسيقات --> <link rel="stylesheet" href="style.css"> <!-- عنوان الصفحة --> <title>اقتراح الوصفة</title> </head> <main class="suggestion"> <!-- اسم الوصفة --> <h2><?= $name ?></h2> <!-- صورة للوصفة --> <img src="<?= $image_url ?>" /> <!-- المكونات وطريقة التحضير --> <div class="recipe" dir="rtl"><?= $recipe ?></div> <footer> <!-- زر الرجوع للصفحة الرئيسية --> <a href="/"> <button>وصفة جديدة ???</button> </a> </footer> </main> أما ملف التنسيقات style.css يهتم بالخطوط والألوان وتنسيق العناصر ضمن الصفحات ليجعلها أجمل ويحوي التالي: /* تنسيق الخطوط */ @import url("https://fonts.googleapis.com/css2?family=Noto+Kufi+Arabic:wght@400;500;700&family=Noto+Naskh+Arabic:wght@400;500;700&display=swap"); @font-face { font-family: "Flags"; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/notocoloremoji/v24/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.0.woff2) format("woff2"); unicode-range: U+1f1e6-1f1ff; } :root { --font-heading: "Noto Kufi Arabic", "Flags", sans-serif; --font-body: "Noto Naskh Arabic", "Flags", sans-serif; } * { font-family: var(--font-body); } h1, h2, button, label, legend { font-family: var(--font-heading); } /* تنسيقات عامة */ body { display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: x-large; min-height: 90vh; background-color: rgb(237, 240, 244); direction: rtl; } /* تنسيق عناصر الصفحة الرئيسية */ fieldset { border: none; padding: 0; } .logo { border-radius: 999rem; background-color: seagreen; width: 11rem; height: 11rem; text-align: center; line-height: 2em; font-size: 6rem; } form { display: flex; flex-direction: column; gap: 1.5rem; } label, legend { display: block; margin-bottom: 1rem; } input[type="radio"] ~ label { font-family: var(--font-body); display: inline-block; margin-bottom: 0; } button, input { padding: 1rem 2rem; font-size: 1.2rem; } input[type="text"] { display: block; width: 100%; } button { border: none; background-color: seagreen; color: white; font-size: 1.5rem; cursor: pointer; transition: all 150ms ease-in-out; } button:hover { background-color: darkgreen; } /* تنسيقات صفحة النتيجة */ .suggestion { display: flex; flex-direction: column; gap: 2rem; margin: 7rem 3rem; max-width: 50ch; } .suggestion h2 { text-align: center; color: #28774a; } .suggestion img { object-fit: cover; border: 1rem solid white; aspect-ratio: 1/1; } .suggestion .description { background-color: white; padding: 3rem; border: 1px solid darkgray; white-space: pre-line; } .suggestion a button { width: 100%; } يمكننا الآن معاينة الصفحات عبر خادم ويب، ويمكننا خلال مرحلة التطوير الاستفادة من خادم الويب الذي توفره بيئة PHP ويأتي مثبتًا معها، وذلك بتنفيذ الأمر php داخل مجلد المشروع مع الخيار S- ونمرر له عنوان الخادم ورقم المنفذ الذي سيتمع إليه، ويمكننا استخدام العنوان المحلي localhost ورقم المنفذ الافتراضي لخوادم الويب 80 ليصبح الأمر كالتالي: php -S localhost:80 يمكننا الآن زيارة العنوان http://localhost:80 من المتصفح ليعالج الخادم افتراضيًا ملف الصفحة الرئيسية index.php ويرسله لنا لنرى التالي: ولمعاينة صفحة النتيجة نختار خيار عشوائي ونضغط "اقترح وصفة"، ويمكننا مؤقتًا إسناد قيم لمتغيرات المتحوى ضمن صفحة suggestion.php لنتمكن من معاينة تنسيقها كالتالي: <!-- suggestion.php --> <?php $name = 'اسم الوصفة'; $description = 'شرح الوصفة...'; $image_url = '#'; ?> لتظهر لنا الصفحة بالمظهر التالي: بذلك أصبحت واجهة التطبيق جاهزة وتعمل وينقصها المحتوى فقط، وهو ما سنبدأ بتطويره في الفقرات القادمة. توليد مفتاح الواجهة البرمجية API والوصول إليه يحتاج تطبيقنا للتواصل مع نماذج الذكاء الاصطناعي المختلفة لتوليد وطلب المحتوى، ويتم ذلك عبر الواجهة البرمجية API التي توفرها الشركة صاحبة تلك النماذج، لذا نحتاج بدايةً لإنشاء حساب على منصتها وإعداد وسيلة الدفع على الحساب لنتمكن من التعامل مع خدماتها المدفوعة، بعدها علينا توليد مفتاح استيثاق خاص لتطبيقنا من صفحة إعداد المفاتيح ليتمكن من إرسال الطلبات، ويمكن تسمية ذلك المفتاح للتعرف عليه لاحقًا، وبعدها سيظهر لنا المفتاح الجديد لمرة واحدة فقط، لذا يجب التأكد من نسخه وحفظه في مكان آمن حيث سنحتاج لتمريره إلى تطبيقنا. يجب التأكيد على ضرورة الحفاظ على ذلك المفتاح وعدم تسريبه، حيث أن الخدمات التي ستتم باستخدامه ستقتطع كلفتها من حساب المستخدم صاحب المفتاح، ويفضل عدم وضع هذه المفاتيح ضمن الشيفرة البرمجية مباشرة، وأفضل طريقة لتمريرها للتطبيق تكون عبر متغيرات البيئة، فيمكننا مثلًا ضمن بيئة التطوير المحلية تعيين متغير بيئة بالاسم OPENAI_API_KEY وذلك ضمن سطر الأوامر وضمن نفس الجلسة قبل تشغيل خادم الويب، ويمكن ذلك ضمن نظام ويندوز مثلًا بتنفيذ الأمر set مع تبديل قيمة المفتاح: set OPENAI_API_KEY=المفتاح وضمن نظام التشغيل لينكس أو ماك بتنفيذ الأمر export كالتالي: export OPENAI_API_KEY=المفتاح ملاحظة: لم نضع قيمة هذا المفتاح في ملف env. فحينذاك سنضطر للاعتماد على مكتبة لتحميل هذا الملف، لذا حاولت أن يكون التطبيق بسيطًا جدًا بلا مكتبات ما أمكن. بعد تنفيذ ذلك الأمر وحصرًا ضمن نفس جلسة سطر الأوامر أي في نفس النافذة يمكن تشغيل خادم الويب، لنتمكن ضمن تطبيقنا في PHP الوصول لذلك المفتاح عبر التابع getenv كالتالي: getenv('OPENAI_API_KEY'); // قيمة المفتاح من متغير البيئة حيث يجب إرسال قيمة ذلك المفتاح مع كل طلب HTTP للواجهة البرمجية، وضمن الترويسة Authorization قيمتها الكلمة Bearer ثم مسافة وبعدها قيمة المفتاح، ويمكن بناء نص تلك الترويسة في PHP عبر استخراج قيمة المفتاح من متغير البيئة كالتالي: "Authorization: Bearer " . getenv('OPENAI_API_KEY'); وبذلك أصبحنا جاهزين لإرسال الطلبات عبر الواجهة البرمجية API للتخاطب مع نماذج الذكاء الاصطناعي لتوليد المحتوى لتطبيقنا وهو ما سنتعرف عليه ضمن الفقرة التالية. توليد الوصفة وصورة لها سنبدأ بتعريف بعض التوابع ضمن صفحة النتيجة suggestion.php سنستدعيها لاحقًا لإسناد قيم المحتوى التي ستولده نماذج الذكاء الاصطناعي إلى المتغيرات التي جهزناها سابقًا لعرضها ضمن قالب الصفحة، ونبدأ بتعريف تابع توليد النصوص chatGPT والذي يقبل متغير التعليمة النصية prompt$ كمعامل له سيرسلها ضمن طلب HTTP ليطلب من نموذج بالاسم gpt-3.5-turbo توليد جواب نصي من تلك التعليمة، يمكن التعامل مع هذا النموذج بصيغة دردشة بين المستخدم والنموذج، في حالتنا لن نحتاج سوى لرسالة واحدة من طرف المستخدم سنبنيها بناءًا على مدخلات المستخدم لاقتراح وصفة وشرح عنها. يرسل الطلب مع ترويسة الاستيثاق التي تحوي مفتاح الواجهة البرمجية API كما تعرفنا سابقًا، وترويسة للتعريف بنوع المحتوى بجسم الطلب Content-type وهو application/json، وبالطريقة POST ويحوي جسم الطلب على اسم النموذج ضمن الحقل model ومصفوفة رسائل الدردشة الحالية بين المستخدم والنموذج ضمن الحقل messages ، حيث تحتوي كل رسالة على اسم صاحب الرسالة ضمن الحقل role ومحتوى الرسالة ضمن content، وفي حالتنا نحتاج لرسالة واحدة فقط من طرف المستخدم أي user ومحتواها هو التعليمة المٌمررة للتابع ضمن المتغير prompt$ ، وبالاستفادة من التابعين file_get_contents و stream_context_create في PHP يمكننا إرسال طلب HTTP دون الحاجة لأي مكتبات إضافية، أما صيغة الجواب فهي JSON بالشكل التالي وما يهمنا منها هو رسالة النموذج: { ... "choices": [{ ... "message": { "role": "assistant", "content": "...", // رسالة النموذج }, }], } يمكننا استخراجها بعد تمرير جواب الطلب للتابع json_decode لتحويله لكائن PHP نستخرج منه القيمة التي نريدها، ليصبح التابع كاملًا كالتالي: /** * توليد نص باستخدام نموذج الدردشة * @param string $prompt التعليمة * @return string النص المُولد */ function chatGPT($prompt) { $result = file_get_contents('https://api.openai.com/v1/chat/completions', false, stream_context_create([ 'http' => [ 'header' => "Content-type: application/json\r\n" . "Authorization: Bearer " . getenv('OPENAI_API_KEY'), 'method' => 'POST', 'content' => json_encode([ 'model' => "gpt-3.5-turbo", // اسم النموذج 'messages' => [ ['role' => "user", 'content' => $prompt], // رسالة المستخدم ] ]) ] ])); // استخراج النتيجة return json_decode($result)->choices[0]->message->content; } ولتوليد الصور سنعرف التابع dalle والذي يقبل وصفًا نصيًا لمحتوى الصورة، ويرسلها في طلب HTTP بالطريقة POST لمسار توليد الصور الخاص، حيث نحتاج فقط ضمن جسم الطلب لتمرير توصيف الصورة ضمن الحقل prompt ولتحديد قياس الصورة في حالتنا هو 512x512 وعدد الصور التي نحتاجها في حالتنا يكفي صورة واحدة لذا نحدد قيمة n بواحد، ولا ننسى ترويستي الاستيثاق ونوع جسم الطلب، أما عن شكل جواب الطلب فهو بصيغة JSON يهمنا منه رابط الصورة التي تم توليدها: { ... "data": [ { "url": "https://..." // رابط الصورة } ] } نمرره أيضًا للتابع json_decode لتحويلها لكائن PHP ونستخرج منه رابط الصورة التي تم توليدها ليصبح التابع كاملًا كالتالي: /** * توليد صورة باستخدام نموذج توليد الصور * @param string $prompt وصف للصورة * @return string رابط للصورة المُولّدة */ function dalle($prompt) { $result = file_get_contents('https://api.openai.com/v1/images/generations', false, stream_context_create([ 'http' => [ 'header' => "Content-type: application/json\r\n" . "Authorization: Bearer " . getenv('OPENAI_API_KEY'), 'method' => 'POST', 'content' => json_encode([ "n" => 1, // عدد الصور "size" => "512x512", // قياس الصورة 'prompt' => $prompt, // توصيف الصورة ]) ] ])); // استخراج رابط الصورة return json_decode($result)->data[0]->url; } لنبدأ باستخدام تلك التوابع لتوليد محتوى الوصفة المقترحة ونعرف التابع recipe والذي سيقبل اسم المطبخ الذي حدده المستخدم كمعامل أول والمكونات التي أدخلها، حيث سنجمع هاتين القيمتين ضمن تعليمة نشرح بها للنموذج ما نريد أن يولده لنا، ليكون قالب هذه التعليمة كالتالي: بحيث نبدل اسم المطبخ والمكونات بالقيم الممررة للتابع، ونمرر تلك التعليمة للتابع chatGPT الذي عرفناه سابقًا، ليعيد لنا اقتراح النموذج، نعالج ذلك الاقتراح باستخراج اسم الوصفة من أول سطر منه بعد تقسيمه باستخدام التابع explode وبقية الأسطر ستحوي على شرح مكونات وطريقة تحضير الوصفة نحددها باستخدام التابع array_slice ونجمعها مجددًا باستخدام التابع implode، ونعيد هاتين القيمتين بعد تمريرهما للتابع trim لإزالة أي محارف ومسافات زائدة، كنتيجة لاستدعاء التابع تحوي الحقل name اسم الوصفة والحقل description شرح الوصفة ليكون التابع كالتالي: /** * اقتراح وصفة * @param string $cuisine اسم المطبخ الذي تنتمي إليه الوصفة * @param string|null $ingredients مكونات اختيارية تتضمنها الوصفة * @return array اسم وشرح الوصفة */ function recipe($cuisine, $ingredients) { // التوصيف $rules = implode(' ', [ // تحديد المطبخ "من وصفات المطبخ ال" . $cuisine . " المشهورة", // شرط المكونات $ingredients ? "يمكن تحضيرها بالمكونات " . $ingredients : // المكونات "", // بلا مكونات محددة ]); // بناء رسالة طلب المستخدم $recipe_prompt = "اقترح وصفة $rules اذكر اسم الوصفة ثم المكونات ثم طريقة التحضير"; // توليد الاقتراح $suggestion = chatGPT($recipe_prompt); // تقسيم الاقتراح لأسطر $lines = explode("\n", $suggestion); return [ // استخراج اسم الوصفة 'name' => trim(str_replace("اسم الوصفة:", "", $lines[0])), // استخراج المكونات والتفاصيل 'description' => trim(implode("\n", array_slice($lines, 1))), ]; } ولتوليد صورة للوصفة المقترحة يجب أن نصف بصريًا ماذا تحوي تلك الصورة، فنذكر المكونات التي تظهر والطبق والمشهد ونمرر ذلك الوصف للتابع dalle الذي عرفناه سابقًا، لكن المشكلة أننا نحتاج لكتابة توصيف خاص بكل وصفة مقترحة من أين سنأتي بذلك التوصيف؟ بما أن التوصيف عبارة عن نص لما لا نطلب ذلك من GPT حيث نمرر له اسم الوصفة مع شرحها، مع الأخذ بالاعتبار أن التوصيف يجب أن يكون باللغة الإنكليزية حيث لحين تاريخ كتابة هذا المقال لا يفهم DALL-E سوى التعليمات باللغة الإنكليزية، فنرسل التعليمة التالية لتوليد وصف للصورة: وترجمتها هي التالي: نعرف التابع recipeImage الذي يقبل اسم وشرح الوصفة ويطلب من GPT شرح لصورة تحوي طبق منها، ثم يمرر ذلك الوصف إلى DALL-E لتوليد الصورة والحصول على رابطها ونعيده كنتيجة لتنفيذ التابع كالتالي: /** * توليد صورة من اسم وتفاصيل وصفة * @param mixed $name اسم الوصفة * @param mixed $description شرح الوصفة * @return string رابط صورة للوصفة */ function recipeImage($name, $description) { // توليد وصف للصورة $image_desc = chatGPT("write a two sentence description of a dish that contains the following recipe \n $name \n $description"); // توليد الصورة return dalle($image_desc); } أصبحت جميع التوابع اللازمة جاهزة، لنستخدمها لتوليد المحتوى وإسناد القيم لمتغيرات واجهة المستخدم لعرضها، حيث يمكن استخراج القيم التي أدخلها المستخدم من المتغير العام POST_$ بحسب اسم الحقول في الصفحة الرئيسية، ونمرر تلك القيم بداية للتابع recipe لاقتراح وصفة جديدة، ثم نستخرج اسم وشرح الوصفة ونسندها للمتغيرات name$ و description$، ولتوليد الصورة نمرر تلك المتغيرات للتابع recipeImage ونسند قيمة رابط الصورة المولدة التي سيعيدها للمتغير image_url$ كالتالي: // اقتراح وصفة من مدخلات المستخدم $recipe = recipe($_POST['cuisine'], $_POST['ingredients']); $name = $recipe['name']; // اسم الوصفة $description = $recipe['description']; // شرح الوصفة $image_url = recipeImage($name, $description); // رابط صورة الوصفة أصبح التطبيق جاهزًا لنختبر بعض النتائج، لا ننسى التأكد من تعيين المفتاح ضمن متغيرات البيئة قبل إعادة تشغيل خادم الويب مجددًا، والتالي بعض النتائج: وصفة فتة حمص بالطحينة وصفة الكبسة ونلاحظ الأجواء العربية ضمن الصورة وصفة الطاجين المغربي وصفة الكشري وبعض صور الوجبات التي استطاع توليدها: تطويرات إضافية ممكنة ونقاط الضعف الحالية يمكن تطوير التطبيق بإضافة بعض المزايا إليه، مثلًا إضافة خيار لتحديد نوع الوصفة (حلويات - وجبات - مشروبات)، أو إضافة عدد السعرات الحرارية التي ستحتويها الوجبة، أو تحديد عدد الأشخاص وبالتالي ستعدل المقادير من قبل النموذج لمراعاة ذلك، أو حفظ الوجبات ضمن مفضلة لكل مستخدم. يُلاحظ أحيانًا اختراع النموذج لوصفات غير موجودة في الواقع، أو شرح طريقة تحضيرها بطريقة خاطئة، وهذا طبيعي نتيجة ضعف المحتوى العربي الذي دُربت عليه تلك النماذج، قد تُطور لاحقًا وتصبح أقوى من تلك الناحية، لذا لا تعتمد على نتائجه دومًا ونبه المستخدمين لذلك، ويمكن تحسين جودة المحتوى عبرة تغيير صيغ التعليمات المرسلة للنماذج فلا زلنا نكتشف العديد من التقنيات والأساليب للتعامل معها للحصول على نتائج أفضل ما يمكن. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن الختام تعرفنا في هذا المقال على كل من نموذج توليد النصوص GPT ونموذج توليد الصور DALL-E واستفدنا من قوة وسهولة استخدامهما في إضافة بعد جديد لتفاعل المستخدم مع التطبيق والحصول على نتائج لا حصر لها تناسب المستخدم، يمكنك توظيف قوة تلك النماذج في العديد من الأفكار فقط أطلق العنان لخيالك. اقرأ أيضًا إعداد شبكة عصبية صنعية وتدريبها للتعرف على الوجوه تصنيف الشخصيات بالاعتماد على تغريداتهم العربية
-
لا يمكن مساعدتك في حل الأسئلة أو الواجبات، يرجى شرح مشكلة محددة تواجهها مع إرفاق الشيفرة المتعلقة بها فقط لنتمكن من المساعدة
-
يمكنك كتابة الشيفرات بلغة PHP داخل الوسم التالي: <?php /* php هنا */ ?> ولطباعة قيمة المتغير يمكنك استخدام echo وتمرير المتغير له، لتصبح طريقة عرض العنوان كالتالي: <h1>Welcom <?php echo $UserName ?></h1> واختصارًا يمكن كتابة الوسم التالي والذي سيطبع قيمة العبارة البرمجية داخله مباشرةً: <?= /* php هنا */ ?> ليصبح العنوان كالتالي: <h1>Welcom <?= $UserName ?></h1> يمكنك القراءة أكثر عن تلك الوسوم من مرجع موسوعة حسوب،
-
عند شراءك لخادم وهمي خاص VPS ضمن مزودات مثل ديجيتال أوشن DigitalOcean أو OVH قد تحتاج لتخصيص طريقة التقسيم لها، ستختار عادة نظام التشغيل الذي تود استخدامه فقط وسيتم تثبيته على قسم واحد يأخذ مساحة القرص المتوفرة بالكامل، من غير الضروري الالتزام باستخدام خوادم ويب VPS تحوي قسمًا واحدًا فقط، بل يمكن تخصيص الأقسام وإضافة قسم تبادل swap وقسم آخر للبيانات الخاصة بنا حسب ما نريد. توفر عدة مزودات وضعًا للإنقاذ rescue mode وهو نظام تشغيل بديل على قرص صلب منفصل يمكننا من خلاله تعديل نظام التشغيل والقرص الصلب الرئيسيان، في هذا المقال سنعتبر أن القسم الموجود من أحد الأنواع Ext4 أو Ext3 أو Ext2 وهي الحالة الافتراضية عند تثبيت نظام ديبيان أو أوبنتو، ويجب تنفيذ الخطوات ضمن هذا المقال من وضع الإنقاذ فقط وليس من داخل النظام، لذا تأكد بدايةً أن مزود VPS الخاص بك يوفر طرفية بوضع الوصول هذا. عملية تعديل حجم نظام الملفات والتقسيم لا تمسح البيانات الموجودة سابقًا، وذلك في حال كان القياس الجديد أكبر من حجم تلك البيانات، مع ذلك يفضل إجراء نسخ احتياطي على قرص أو جهاز أو خادم خارجي، وسنفرض أننا نريد تقليص حجم القسم من 40 جيجابايت إلى 2 جيجابايت (بفرض البدء من بداية القرص) وذلك لإنشاء أقسام إضافية بعده. الخطوة الأولى: الدخول إلى وضع الإنقاذ rescue mode وتحديد القرص أول خطوة سنقوم بها هي إعادة التشغيل والدخول إلى وضع الإنقاذ، والذي يكون عادة بأحد أنظمة جنو لينكس مثل ديبيان، حيث سيتم إعطاءنا كلمة سر مؤقتة للمستخدم الجذر root لهذا النظام، وندخل إليه عبر SSH. بعدها نحدد القرص المٌستخدم من قبل النظام الأساسي باستخدام الأمر fdisk -l. في حالتنا القرص /dev/sdb هو الذي نريد تعديل مخطط تقسيمه والقسم /dev/sdb1 هو الوحيد لدينا حاليًا، والقرص /dev/sda هو المكان المٌثبت عليه نظام وضع الإنقاذ، وفي حال كان اسم القرص للقسم لديك مختلفًا عن sdb1 يمكنك تبديله خلال تطبيقك للخطوات التالية ضمن المقال. الخطوة الثانية: تقليص نظام الملفات يمكن تنفيذ الأمر e2fsck -f /dev/sdb1 للتحقق من نظام الملفات أولًا، وإلا لن يعمل الأمر resize2fs عند تنفيذه لاحقًا: غالبًا يأتي الأمران e2fsck و resize2fs مثبتان مسبقًا ضمن نظام الإنقاذ، وفي حال لم يكونا كذلك يمكن تثبيت حزمة e2fsprogs على ديبيان بتنفيذ الأمر apt install e2fsprogs ويجب التأكد أيضًا أن حجم نظام الملفات لا يتجاوز 2 جيجابايت، وبفرض أن /mnt فارغ يمكننا تركيب نظام الملفات باستخدام الأمر mount /dev/sdb1 /mnt والتحقق من حجمه باستخدام الأمر df: بعدها يمكن إلغاء تركيب نظام الملفات بتنفيذ الأمر umount /dev/sdb1، وقبل تنفيذ بقية الأوامر وفي حال أجرينا أي تخصيصات على نظام التشغيل أو أضفنا أي بيانات هامة يفضل عندها إجراء نسخ احتياطي لتلك الملفات، بعدها يمكن تنفيذ الأمر التالي resize2fs /dev/sdb1 2G، حيث تشير الوحدة 2G إلى الحجم 2 جيجابايت، يمكننا كتابتها بوحدة الميجابايت كالتالي: 2048M. في الصورة التالية تم تنفيذ الأمر resize2fs على اعتبار أن عدد القطاعات المُمرر سيُفهم على أنه 2000M أي 2000 ميجابايت، وبحساب تلك المساحة على أن كل قطاع لديه الحجم 512 بايت، سينتج لدينا عدد القطاعات التالي 4194304 والذي يساوي 2 جيجابايت، حيث يمكن استخدام الطريقتين والحصول على النتيجة نفسها: التقرير ضمن خرج تنفيذ الأمر السابق يخبرنا بتعديل حجم القسم إلى 524288 كتلة كل منها بحجم 4 كيلوبايت، وبحساب الحجم الكلي لها 524288 * 4096 بايت = 2 جيجابايت، وهي النتيجة المطلوبة، حيث أن 4 كيلوبايت هو حجم الكتلة الواحدة على مستوى نظام الملفات، بينما الحجم 512 بايت هو حجم القطاع الواحد على مستوى القرص، ويمكننا التأكد أن الحجم هو 512 بايت من خرج الأمر fdisk الذي نفذناه سابقًا. الخطوة الثالثة: تقليص حجم القسم نفذنا حتى الآن خطوة تقليص حجم نظام الملفات ما يعني أنه لم يعد بإمكاننا إضافة البيانات إليه لأنه لا يوجد مكان لها في الحجم المنطقي له والمساوي 2 جيجابايت، ولكن القسم الموجود ضمنه نظام الملفات لا يزال يشغل مساحة القرص المتوفر كاملةً. يمكن الاستفادة من الأمر parted لإدارة الأقسام في لينكس، حيث يمكن تثبيته على ديبيان بتنفيذ الأمر apt install parted، ويمكن استخدام هذه الأداة تفاعليًا، حيث يمكن تنفيذ الأمر بتمرير المسار /dev/sdb كمعامل له: يمكن استخدام الأمر unit s ليتم إظهار كافة الحجوم بعدد القطاعات داخلها بدلًا من وحدات الحجم مثل ميجابايت MiB أو جيجابايت GiB، وذلك للتحكم بالحجم بأكبر دقة ممكنة، وإلا سنواجه مشاكل مثل ظهور الرسالة التالية التي تعني أن الحجم المحدد لا يقدم أفضل أداء: "The resulting partition is not properly aligned for best performance" ويمكن الاستفادة من الأمر print free لمعرفة حالة القرص: إذا أردنا تحديد حجم القسم رقم 1 إلى 2 جيجابايت ما يعادل عدد القطاعات التالية: 2 * 1024 * 1024 * 1024 / 512 = 4194304 وننفذ الأمر resizepart 1 بعدها سيتنظر منا الأمر تحديد إحداثيات النهاية للقسم على القرص مُعبَّرًا عنها بعدد القطاعات منذ بداية القرص، وبما أنه يوجد منطقة محجوزة قبل القسم تنتهي عند القطاع رقم 2047 يجب إضافة هذا الحجم إلى حجم القسم الحالي 4194304 + 2047 = 4196351s، ومن المهم إضافة اللاحقة s لنهاية القيمة، بعدها نجيب بنعم Yes بعد الرسالة التحذيرية، ثم ننفذ الأمر print free للتأكد من تغيير حجم القسم. قد يتم إعادة القسم إلى حالته التي كان عليها قبل القيام بهذه الإعدادات في حال أعدنا التشغيل الآن، لذا للتحقق من التغييرات يفضل إضافة قسم آخر في المساحة الخالية المتبقية قبل الخروج وإعادة التشغيل. الخطوة الرابعة: إضافة أقسام أخرى بفرض نريد إضافة قسم بحجم 12 جيجابايت ومن نوع Ext4 بعد القسم المخصص لنظام التشغيل مباشرةً، فيجب تعريف ذلك القسم أولًا، وذلك بإضافة سجل ضمن جدول التقسيم في مكان ما داخل المنطقة المحجوزة، نستخدم لذلك الأمر mkpart primary ext4: كما في المثال السابق يمكننا تحديد مكان بدء القسم بعد قطّاع واحد من نهاية القسم الذي يسبقه كي لا نهدر أي مساحة بين القسمين، حيث يكون عنوان البداية هو 4196351 + 1 = 4196352s، وعنوان النهاية بعد 12 * 1024 * 1024 * 1024 / 512 قطاعًا وهو حجم القسم بالكامل ونضيف إليه عدد قطاعات الأقسام التي تسبقه + 4196351 قطاعًا نحصل على = 29362175s قطاع. الآن يمكننا الخروج من parted بالضغط على Ctrl + D ثم إنشاء نظام ملفات من النوع Ext4 على sdb2، ولتطبيق إعدادات Ext4 الافتراضية ننفذ الأمر الآتي: mkfs.ext4 /dev/sdb2 خاتمة أجرينا في هذا المقال تعديلًا على التقسيم لنظام الملفات عبر سطر الأوامر، وركزنا على الحالة التي يحتوي فيها الخادم على قسم كبير واحد افتراضيًا ونريد تقسيمه. ترجمة -وبتصرف- للمقال How to Change the Partition Scheme on Your Linux Server? لفئة من المؤلفين. اقرأ أيضًا مقدمة إلى نظام ملفات لينكس EXT4 كيفية إضافة قرص Swap على أوبنتو إعداد الذاكرة الوهمية (ملفّات التبادل) على خادوم خاص وهمي (VPS)
-
ليس بالضرورة فصل التطبيق إلى العديد من المكونات، للتطبيقات البسيطة ابدأ دومًا بتضمين كل عناصر التطبيق ضمن مكون واحد، مثلًا لنموذج الادخال اكتب العناصر input كلها مباشرةً ضمن مكون Form، بعد فترة ستلاحظ تكرار عناصر ما في أكثر من مكان هنا يمكنك التأكد من أن تلك العناصر قد يكون من الأنسب إخفاء تفاصيل تضمينها ضمن مكون جديد وليكن Input في حالة عناصر الادخال، فصل المكون سيخلق لك مشاكل مثل ضرورة تعريف طريقة لإعلام المكون الأب بالمعلومات التي يحتاجها من ذلك المكون، يمكنك لذلك تمرير توابع للمكون تُنفذ عند أحداث معينة داخل هذا المكون، في حالتك يمكنك تمرير تابع onChange للمكون Input تُعلم بها المكون الأب بتغير القيمة في ذلك الحقل، لتُخزنها في مكان تجميع كل البيانات من كل الحقول في المكون الأب لإرسالها لاحقًا ضمن طلب من نوع POST، الهيكلية العامة لهذا الحل ستكون كالتالي يمكنك فهم طريقة العمل وتطبيقها بنفسك على تطبيقك حسب التفاصيل المطلوبة: function From() { return <div> <Input onChange={value => {/* تخزين القيمة الجديدة */ }} /> <Input onChange={value => {/* تخزين القيمة الجديدة */ }} /> </div> } function Input({ onChange }) { return <input onChange={event => onChange(event.target.value)} ... /> /* إعلام المكون الأب بتغير القيمة */ } يمكنك الاستفادة من قراءة المقال التالي:
-
سنقدم في هذا المقال دليلًا شاملًا لإدارة الحجوم المنطقية LVM في لينكس، حيث سنتعرف على فكرة LVM ومكوناتها والفائدة من تطبيقها، وسنغطي الجانبين النظري والعملي عبر أمثلة عن إنشاء وإدارة الحجوم المنطقية في لينكس، حيث سيكون لديك في نهاية هذا المقال المعلومات الكافية للتطبيق العملي على الحجوم المنطقية. ما هي إدارة الحجوم المنطقية LVM في لينكس LVM هي اختصار لإدارة الحجوم المنطقية أو Logical Volume Management، وهي طريقة بديلة لإدارة أنظمة التخزين عن الطرق التقليدية المُعتمدة على التقسيم، فبدلًا من إنشاء الأقسام على الأقراص يمكن بسهولة إنشاء حجوم منطقية وتركيبها على نظام الملفات كما نفعل عند تقسيم القرص، لكن الاستثناء الوحيد على ذلك هو عدم إمكانية تركيب تلك الأحجام المنطقية على المسار /boot واعتماد أحدها كقرص إقلاع، وذلك لأن محمل الإقلاع GRUB الشائع استخدامه في لينكس لا يدعم قراءة الحجوم المنطقية، ومحمل الإقلاع البديل له systemd-boot لن نتمكن من استخدامه أيضًا لأنه يدعم قراءة أنظمة الملفات من النوع vfat فقط. مكونات LVM يوجد ثلاث مكونات رئيسية ضمن LVM: الحجوم الفيزيائية مجموعات الحجوم الحجوم المنطقية اثنان فقط من تلك المكونات يقابلها نظير في نظام التقسيم التقليدي، الجدول التالي يوضح هذا التقابل: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } نظام تقسيم القرص LVM الأقسام الحجوم المنطقية الأقراص مجموعات الحجوم أما الحجوم الفيزيائية فليس لها نظير مقابل وسنتحدث عنها. لماذا نستخدم LVM؟ الميزة الأساسية من استخدام LVM هي سهولة تغيير حجم أو مجموعة الحجم، فهي توفر طبقة استخدام مجردة عالية المستوى تجعلنا نركز في إدارة مجمع التخزين المركزي فقط، وتتولى هي إدارة الأمور منخفضة المستوى مثل الأقسام وتفاصيل الأقراص الخام، ستلاحظ سهولة استخدام LVM في حال حاولت مسبقًا تقسيم قرص ما واختبرت الصعوبة في ذلك. تحضيرات المثال العملي سنركز في هذا المقال على الشق العملي بجانب النظري، وسننفذ الأوامر أثناء شرحها، لأن التطبيق العملي أفضل وسيلة للتعلم، لذا وفي حال أردت التنفيذ يفضل استخدام آلة افتراضية، لهذا يمكنك الاستفادة من ملف الإعدادات Vagrantfile لتشغيل آلة افتراضية خفيفة باستخدام برنامج VirtualBox، حيث تملك هذه الآلة ثلاث أقراص إضافية يمكن استخدامها لتطبيق الأمثلة في هذا المقال، ويمكنك إنشاء مجلد جديد في مكان ما على جهازك وحفظ المحتوى التالي في ملف بالاسم Vagrantfile: Vagrant.configure "2" do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "lvm" 3.times {|i| config.vm.disk :disk, size: "5GB", name: "drive-#{i}"} config.vm.provider :virtualbox do |machine| machine.memory = 1024 machine.cpus = 1 machine.customize ["modifyvm", :id, "--cpuexecutioncap", "50"] end end # vi: set ft=ruby أو يمكن جلب محتوى هذا الملف باستخدام wget عبر تنفيذ الأمر التالي: wget https://gist.githubusercontent.com/debdutdeb/98ed1b6aef36885d07ce8247188dfd5e/raw/524259da5dfa2d8750883d01c8159829729e224d/Vagrantfile يجب التأكد أولًا من تثبيت Vagrant و VirtualBox بعد حفظ ملف Vagrantfile نُعين قيمة متغير البيئة VAGRANT_EXPERIMENTAL إلى disks عبر تنفيذ الأمر: export VAGRANT_EXPERIMENTAL=disks أخيرًا يمكن تشغيل الآلة الافتراضية باستخدام الأمر التالي مع التأكد من تعديل مسار العمل الحالي إلى المجلد الحاوي على ملف Vagrantfile: vagrant up بعد تشغيل الآلة يمكننا الدخول إليها عبر SSH بتنفيذ الأمر vagrant ssh لتنفيذ الأوامر في هذا المقال، وبعد الانتهاء يمكنك حذف الآلة عبر تنفيذ الأمر vagrant destroy أيضًا من داخل المجلد الحاوي على الملف Vagrantfile. تثبيت LVM قبل تنفيذ الأوامر يجب أولًا تثبيت الحزمة lvm2، وعادة تأتي تلك الحزمة مثبتة مسبقًا في معظم التوزيعات الحديثة خصوصًا المبنية على أوبنتو، ولكن يجب التأكد من تثبيتها أولًا، لذا يمكنك مراجعة توثيق التوزيعة التي تعمل منها للتحقق من طريقة تثبيت الحزمة lvm2. المثال العملي على استخدام LVM بنينا سابقًا آلة افتراضية تملك سعة تخزين 40 جيجابايت في الجذر، وتملك ثلاث أقراص خارجية سعة كل منها 5 جيجابايت وهي ما يهمنا، وتلك السعة عشوائية ولا تعني شيئًا محددًا. vagrant@lvm:~$ lsblk -o name,size,fstype NAME SIZE FSTYPE loop0 55.5M squashfs loop1 32.3M squashfs loop2 70.4M squashfs sda 40G └─sda1 40G ext4 sdb 10M iso9660 sdc 5G sdd 5G sde 5G كما هو واضح من الخرج السابق الأقراص التي سنستخدمها هي sdc و sdd و sde، تذكر أن المكونات الرئيسية الثلاث لـ LVM هي: الحجوم الفيزيائية مجموعات الحجوم الحجوم المنطقية سنشرح تلك المكونات بالترتيب. 1. الحجوم الفيزيائية أول مكون يجب معرفته في LVM هو الحجوم الفيزيائية، وهي الوحدة الأساسية التي تبني طبقة التجريد العليا للحجوم المنطقية، ببساطة تُمثل الحجوم الفيزيائية الوحدات المنطقية لنظام LVM، ويمكن للحجم المنطقي أن يمثل أي شيء، مثل قرص أو قسم من قرص، حيث أن إنشاء وتهيئة حجم فيزيائي يمثلان الأمر نفسه، فكل منهما يعني أننا نٌحضِّر الوحدات الأساسية مثل الأقسام أو الأقراص وغيرها لعمليات لاحقة عليها، ستتوضح هذه الفكرة لاحقًا. الأدوات: جميع أدوات إدارة الحجوم الفيزيائية تبدأ بالأحرف pv وهي اختصار للأحرف الأولى من حجم فيزيائي Physical Volume، مثل pvcreate و pvchange و pvs و pvdisplay وغيرها. كل الأوامر التي سنذكرها من الآن وصاعدًا مدمرة أي لا يمكن الرجوع عنها، لذا إذا لم تكن تطبق تلك الأوامر ضمن بيئة افتراضية أو خادم ضمن السحابة لا يحوي على بيانات مهمة مخزنة ضمنه أو بيانات يمكنه له الوصول إليها، ننصح بالتوقف عن التطبيق وأخذ الحذر. إنشاء حجوم فيزيائية يمكن إنشاء حجوم فيزيائية باستخدام قرص خام غير مقسم بعد أو باستخدام الأقسام الموجودة مسبقًا، وكما ذكرنا سابقًا لدينا ثلاث محركات أقراص خارجية متصلة بالآلة الافتراضية، لنبدأ بالقرص /dev/sdc، حيث يمكن استخدام الأمر pvcreate لإنشاء حجم فيزيائي، وذلك فقط عبر تمرير اسم الجهاز له كالتالي: sudo pvcreate /dev/sdc ليظهر لنا خرج مشابه للتالي: vagrant@lvm:~$ sudo pvcreate /dev/sdc Physical volume "/dev/sdc" successfully created. سنقسم الآن الجهاز /dev/sdd إلى أقسام متساوية، ويمكن استخدام العديد من الأدوات لتنفيذ ذلك مثل cfdisk و parted و fdisk وغيرها: vagrant@lvm:~$ lsblk -o name,size,fstype | grep sdd sdd 5G ├─sdd1 2.5G └─sdd2 2.5G يمكن الآن إنشاء أكثر من حجم فيزيائي من تلك الأقسام بخطوة واحدة وذلك عبر تمرير أسماء تلك الأجهزة معًا إلى الأمر pvcreate كالتالي: sudo pvcreate /dev/sdd1 /dev/sdd2 بتنفيذ الأمر السابق يتم إنشاء القسمين معًا: vagrant@lvm:~$ sudo pvcreate /dev/sdd1 /dev/sdd2 Physical volume "/dev/sdd1" successfully created. Physical volume "/dev/sdd2" successfully created. عرض الحجوم الفيزيائية المتاحة لعرض الحجوم الفيزيائية المتاحة يمكن استخدام أحد الأوامر الثلاثة التالية pvscan و pvs و pvdisplay، دون تمرير أي معاملات أو خيارات لتلك الأوامر كالتالي: تنفيذ الأمر pvscan: vagrant@lvm:~$ sudo pvscan PV /dev/sdc lvm2 [5.00 GiB] PV /dev/sdd1 lvm2 [2.50 GiB] PV /dev/sdd2 lvm2 [<2.50 GiB] Total: 3 [<10.00 GiB] / in use: 0 [0 ] / in no VG: 3 [<10.00 GiB] تنفيذ الأمر pvs: vagrant@lvm:~$ sudo pvs PV VG Fmt Attr PSize PFree /dev/sdc lvm2 --- 5.00g 5.00g /dev/sdd1 lvm2 --- 2.50g 2.50g /dev/sdd2 lvm2 --- <2.50g <2.50g تنفيذ الأمر pvdisplay: vagrant@lvm:~$ sudo pvdisplay "/dev/sdc" is a new physical volume of "5.00 GiB" --- NEW Physical volume --- PV Name /dev/sdc VG Name PV Size 5.00 GiB Allocatable NO PE Size 0 Total PE 0 Free PE 0 Allocated PE 0 PV UUID SzSkdD-xKYa-4y7P-teyU-481p-uiQ8-qieMJJ "/dev/sdd1" is a new physical volume of "2.50 GiB" --- NEW Physical volume --- PV Name /dev/sdd1 VG Name PV Size 2.50 GiB Allocatable NO PE Size 0 Total PE 0 Free PE 0 Allocated PE 0 PV UUID 553Iy4-JJ21-LfIw-udtO-j9Cd-7gFS-iXXFVS "/dev/sdd2" is a new physical volume of "<2.50 GiB" --- NEW Physical volume --- PV Name /dev/sdd2 VG Name PV Size <2.50 GiB Allocatable NO PE Size 0 Total PE 0 Free PE 0 Allocated PE 0 PV UUID bf7ghn-QkPm-EUdp-GdyW-shMG-5sMn-VhNtYB كما نلاحظ من الخرج تظهر لنا الحجوم الفيزيائية المتاحة مع معلومات مفيدة خاصة بها. إزالة حجم فيزيائي يمكن إزالة حجم فيزيائي باستخدام الأمر pvremove، وذلك بشكل مشابه لاستخدام الأمر pvcreate، حيث نمرر أسماء الأجهزة التي هيئناها كحجوم فيزيائية إلى الأمر pvremove، وسنطبق ذلك عبر إزالة الجهاز /dev/sdd2 من قائمة الحجوم كالتالي: sudo pvremove /dev/sdd2 الخرج سيكون مطابقًا للتالي: vagrant@lvm:~$ sudo pvremove /dev/sdd2 Labels on physical volume "/dev/sdd2" successfully wiped. يمكن التأكد من عملية الإزالة بعرض قائمة الحجوم الفيزيائية المتاحة عبر تنفيذ الأمر sudo pvs: vagrant@lvm:~$ sudo pvs PV VG Fmt Attr PSize PFree /dev/sdc lvm2 --- 5.00g 5.00g /dev/sdd1 lvm2 --- 2.50g 2.50g نلاحظ عدم ظهور الحجم /dev/sdd2 أي تمت إزالته. 2. مجموعات الحجوم مجموعات الحجوم تُعبّر عن مجموعة من الحجوم الفيزيائية، وهي الطبقة الثانية من التجريد التي يوفرها LVM، مجموعات الحجوم هي مجمع التخزين الذي يدمج معًا سعات التخزين لعدة أجهزة. الأدوات: تبدأ أسماء جميع أدوات مجموعة الحجم بالأحرف vg وهي الأحرف الأولى من مجموعة حجم Volume Group، مثل vgcreate و vgs و vgrename وغيرها. إنشاء مجموعات الحجم يمكن إنشاء مجموعات الحجم باستخدام الأمر vgcreate، حيث نمرر له كمعامل أول اسم يتم إسناده لمجموعة الحجم، والمعاملات البقية هي الحجوم الفيزيائية التي سنضمها إلى مجمع التخزين: sudo vgcreate lvm_tutorial /dev/sdc /dev/sdd1 مثال: vagrant@lvm:~$ sudo vgcreate lvm_tutorial /dev/sdc /dev/sdd1 Volume group "lvm_tutorial" successfully created عرض مجموعات الحجم بشكل مشابه لطريقة عرض الحجوم الفيزيائية يمكننا عرض مجموعات الحجم باستخدام أوامر مختلفة تُقدِّم كل منها درجة من مختلفة التفاصيل بعد تنفيذها، أوامر مثل vgdisplay و vgscan و vgs: تنفيذ الأمر vgs: vagrant@lvm:~$ sudo vgs VG #PV #LV #SN Attr VSize VFree lvm_tutorial 2 0 0 wz--n- 7.49g 7.49g تنفيذ الأمر vgscan: vagrant@lvm:~$ sudo vgscan Found volume group "lvm_tutorial" using metadata type lvm2 تنفيذ الأمر vgdisplay: vagrant@lvm:~$ sudo vgdisplay --- Volume group --- VG Name lvm_tutorial System ID Format lvm2 Metadata Areas 2 Metadata Sequence No 1 VG Access read/write VG Status resizable MAX LV 0 Cur LV 0 Open LV 0 Max PV 0 Cur PV 2 Act PV 2 VG Size 7.49 GiB PE Size 4.00 MiB Total PE 1918 Alloc PE / Size 0 / 0 Free PE / Size 1918 / 7.49 GiB VG UUID LYVE9P-vY0G-OAW6-an8q-yfBx-rrB1-YU61m1 عرض الحجوم الفيزيائية المرتبطة بمجموعة حجم معينة يمكننا عرض الحجوم الفيزيائية المرتبطة بمجموعة حجم معينة عبر تنفيذ الأمر التالي: sudo pvdisplay -S vgname=<volume_group_name> -C -o pv_name مثال: vagrant@lvm:~$ sudo pvdisplay -S vgname=lvm_tutorial -C -o pv_name PV /dev/sdc /dev/sdd1 يمكن أيضًا إظهار عدد الحجوم الفيزيائية كالتالي: sudo vgdisplay -S vgname=<volume_group_name> -C -o pv_count مثال: vagrant@lvm:~$ sudo vgdisplay -S vgname=lvm_tutorial -C -o pv_count #PV 3 توسيع مجموعة حجم توسيع مجموعة حجم يعني إضافة حجوم فيزيائية إضافية إلى تلك المجموعة، حيث يمكن استخدام الأمر vgextend لتنفيذ ذلك، وصيغة تنفيذ هذا الأمر بسيطة وهي: vgextend <volume_group> <physical_volume1> <physical_volume2> .... لننفذ ذلك بتوسيع المجموعة lvm_tutorial وإضافة الحجم /dev/sdd2 لها. sudo vgextend lvm_tutorial /dev/sdd2 لاحظ خرج الأمر التالي: vagrant@lvm:~$ sudo vgextend lvm_tutorial /dev/sdd2 Physical volume "/dev/sdd2" successfully created. Volume group "lvm_tutorial" successfully extended أزلنا في فقرة سابقة الجهاز /dev/sdd2 من الحجم الفيزيائي، لذا قبل إضافة أي جهاز إلى مجموعة الحجم يجب تهيئته كحجم فيزيائي أولًا، ليتمكن LVM من إدارته كقسم من المجموعة، لذا جهّز الأمر vgextend لنا الجهاز /dev/sdd2 كحجم فيزيائي قبل إضافته إلى المجموعة، وللتأكد يمكننا الآن عرض الحجوم الفيزيائية المرتبطة بمجموعة حجم هذه: sudo pvdisplay -S vgname=lvm_tutorial -C -o pv_name الخرج: vagrant@lvm:~$ sudo pvdisplay -S vgname=lvm_tutorial -C -o pv_name PV /dev/sdc /dev/sdd1 /dev/sdd2 نلاحظ كيف تم إضافة الجهاز /dev/sdd2 إلى قائمة الأجهزة المرتبطة بالمجموعة كما هو متوقع. تقليص مجموعة حجم على عكس عملية توسيع مجموعة حجم، فإن تقليص مجموعة حجم تعني إزالة حجم فيزيائي أو أكثر من تلك المجموعة، حيث يمكننا استخدام الأمر vgreduce لتنفيذ ذلك، وصيغة الأمر تكون كالتالي: vgreduce <vgname> <physical_volume1> <physical_volume2> .... لنجرب إزالة الحجوم الفيزيائية /dev/sdc و /dev/sdd1 كالتالي: sudo vgreduce lvm_tutorial /dev/sdc /dev/sdd1 مثال: vagrant@lvm:~$ sudo vgreduce lvm_tutorial /dev/sdc /dev/sdd1 Removed "/dev/sdc" from volume group "lvm_tutorial" Removed "/dev/sdd1" from volume group "lvm_tutorial" لن نتمكن من تقليص مجموعة الحجم في حال كان يوجد ضمنها أي حجوم منطقية فعالة، والآن نعرض قائمة الحجوم الفيزيائية الموجودة: sudo pvdisplay -S vgname=lvm_tutorial -C -o pv_name الخرج: vagrant@lvm:~$ sudo pvdisplay -S vgname=lvm_tutorial -C -o pv_name PV /dev/sdd2 نلاحظ كيف تم إزالة الحجمين الفيزيائيين من المجموعة، لكننا سنحتاج لوجود تلك الحجوم ضمن المجموعة في الأمثلة لاحقة، لذا سنعيدها إلى المجموعة مجددًا بتنفيذ الأمر: sudo vgextend lvm_tutorial /dev/sdc /dev/sdd1 إزالة مجموعة حجم يمكن إزالة حجم منطقي باستخدام الأمر vgremove كالتالي: sudo vgremove lvm_tutorial لا تنفذ الأمر حاليًا كي لا تضطر لإعادة إنشاء مجموعة الحجم مجددًا، لكن يمكنك اختبار تنفيذ هذا الأمر في نهاية المقال إذا أردت. 3. الحجوم المنطقية وهي ما نتعامل معه في أغلب الوقت، الحجم المنطقي يشبه القسم، لكن الفرق بينهما أن الأقسام تُبنى فوق القرص مباشرة، أما الأحجام المنطقية فهي تُبنى فوق مجموعة الحجم، وهي تمكننا من: تنسيق الحجم المنطقي بأي نظام ملفات نريد. تركيبه في أي مكان نريده ضمن نظام الملفات. سنتعلم في هذا القسم الأمور التالية: كيف ننشئ الحجوم المنطقية. إجراء عمليات شائعة على الحجوم المنطقية. تعديل مساحة حجم منطقي. إزالة حجم منطقي. الأدوات: تبدأ أسماء جميع أدوات الحجوم المنطقية بالأحرف vl وهي الأحرف الأولى من حجم منطقي Logical Volume، مثل lvcreate و lvs و lvreduce وغيرها. إنشاء حجوم منطقية يمكن إنشاء الحجوم المنطقية باستخدام الأمر lvcreate، وصيغة استخدامه هي كالتالي: sudo lvcreate -L <size> -n <lvname> <vgname> لنشرح أجزاء ذلك الأمر: الخيار -L يدل على الحجم الجديد للحجم المنطقي، حيث يمكن استخدام عدد متبوعًا بوحدة الحجم مثل "GB" للجيجابايت أو "MB" للميجابايت أو "KB" للكيلو بايت، مثال "1GB" وتعني 1 جيجابايت. الخيار -n يستخدم لتسمية الحجم المنطقي. أخيرًا سنحتاج لتمرير اسم مجموعة الحجم التي سينضم الحجم المنطقي هذا إليها، لذا يجب التأكد من أن الحجم المحدد للحجم المنطقي هذا متاح كمساحة فارغة ضمن مجموعة الحجم التي سينضم إليها. ننفذ الأمر التالي: sudo lvcreate -L 5GB -n lv1 lvm_tutorial الخرج: vagrant@lvm:~$ sudo lvcreate -L 5GB -n lv1 lvm_tutorial Logical volume "lv1" created. عمليات شائعة على حجم منطقي كما ذكرنا سابقًا يمكن وضع نظام ملفات ضمن حجم منطقي وتركيبه أيضًا في أي مكان على نظام الملفات، فبعد إنشاءه يمكننا العثور على الحجم المنطقي ضمن المسار /dev/<vgname>/<lvname>، في مثالنا سيكون الحجم متاحًا على المسار /dev/lvm_tutorial/lv1. vagrant@lvm:~$ ls -l /dev/lvm_tutorial/lv1 lrwxrwxrwx 1 root root 7 May 17 02:09 /dev/lvm_tutorial/lv1 -> ../dm-0 يمكننا استخدامه الآن كأي قسم اعتيادي، حيث يمكننا تنسيقه بتنسيق ext4 كالتالي: sudo mkfs.ext4 /dev/lvm_tutorial/lv1 وتركيبه في مكان ضمن بنية المجلدات الحالية مثل /mnt: sudo mount -t ext4 /dev/lvm_tutorial/lv1 /mnt تعديل مساحة حجم منطقي يمكن توسيع حجم منطقي باستخدام الأمر lvextend وتقليص حجمه باستخدام الأمر lvreduce، ويمكننا بدلًا من ذلك استخدام الأمر lvresize فقط لتنفيذ كلا العمليتين، أولًا نتأكد من توفر مساحة كافية في مجموعة الحجم: sudo vgs -S vgname=lvm_tutorial -o vg_free الخرج: vagrant@lvm:~$ sudo vgs -S vgname=lvm_tutorial -o vg_free VFree <4.99g بحسب الخرج السابق يتوفر لدينا مساحة كافية، لذا سنزيد الحجم بمقدار 2 جيجابايت، وتذكر بأن الحجم المنطقي لا يزال مركبًا ضمن المسار /mnt. vagrant@lvm:~$ mount | grep '/mnt' /dev/mapper/lvm_tutorial-lv1 on /mnt type ext4 (rw,relatime) باستخدام الأمر التالي يمكننا تعديل مساحة الحجم المنطقي: sudo lvresize -L +2GB lvm_tutorial/lv1 صيغة الأمر العامة تكون كالتالي: lvresize -L [+|-][Size] <vgname>/<lvname> الرموز + أو - بعد الخيار -L نضيفها بحسب العملية التي سنجريها إذا كانت زيادة أو تقليص للحجم على الترتيب، فبعد زيادة الحجم يجب تعديل حجم نظام الملفات أيضًا، فلنظام ملفات من نوع ext4 يمكن استخدام الأمر resize2fs كالتالي: sudo resize2fs /dev/lvm_tutorial/lv1 الخرج: vagrant@lvm:~$ sudo resize2fs /dev/lvm_tutorial/lv1 resize2fs 1.45.5 (07-Jan-2020) Filesystem at /dev/lvm_tutorial/lv1 is mounted on /mnt; on-line resizing required old_desc_blocks = 1, new_desc_blocks = 1 The filesystem on /dev/lvm_tutorial/lv1 is now 1835008 (4k) blocks long. لا تدعم كل أنظمة الملفات التعديل الساخن، ومن الأنظمة التي تدعم ذلك هي Ext4 و XFS، لذا ينصح باستخدامها دومًا، عملية تقليص مساحة حجم افتراضي مهمة معقدة قليلًا ولن نشرحها في هذا المقال. إزالة حجم منطقي يمكن إزالة حجم منطقي باستخدام الأمر lvremove، وصيغته تكون كالتالي: lvremove <vgname>/<lvname> بتنفيذ هذا الأمر: sudo lvremove lvm_tutorial/lv1 نحصل على الخرج: vagrant@lvm:~$ sudo lvremove lvm_tutorial/lv1 Do you really want to remove and DISCARD active logical volume lvm_tutorial/lv1? [y/n]: y Logical volume "lv1" successfully removed خاتمة يوجد الكثير من العمليات التي يمكن تنفيذها على الحجوم المنطقية والفيزيائية ومجموعات الحجوم، وهي أكثر من أن يتم حصرها ضمن مقال واحد، حيث تبقى لدينا قرص واحد داخل الآلة الافتراضية لم ننفذ عليه أي عمليات وهو /dev/sde، يمكن استخدامه للتدرب على بعض الأوامر التي شرحناها في هذا المقال، كإنشاء مجموعة حجم جديدة وتوسيع حجم مجموعة موجودة مسبقًا. ترجمة -وبتصرف- للمقال The Complete Beginner's Guide to LVM in Linux لصاحبه Debdut Chakraborty. اقرأ أيضًا مدخل إلى مدير وحدات التّخزين المنطقيّة في نظام لينكس LVM مدخل إلى مصطلحات ومفاهيم التخزين في لينكس إدارة أجهزة التخزين بآليّة LVM على أوبونتو: عرض المكوّنات.