פיצ'רים חדשים ב Ruby מהשנים האחרונות (גירסאות 2.5 ו 2.6)
גירסא חדשה של רובי יוצאת כל שנה בערך, עם רובי 2.5 שיצאה במרץ 2018 ואחריה בדצמבר יצאה 2.6. התוכנית היא לשחרר את 2.7 בדצמבר השנה, מה שנותן לנו כמה חודשים להנות משתי הגירסאות האחרונות של 2018. נעבור לראות כמה יכולות חדשות שלהן כדי שגם לכם יהיה חשק לשדרג.
1. רובי לא יחפש יותר קלאסים במקום הלא נכון
ברובי 2.5 הקוד הבא סוף סוף ייכשל:
class Staff; end
class ItemsController; end
puts Staff::ItemsController
ואני יודע שזה לא נשמע כמו איזה דבר גדול כי הרי ברור שזה אמור להיכשל: בשום שלב לא הגדרנו את Staff::ItemsController. אבל מסתבר שהחיים לא כל כך פשוטים. ב Ruby עד גירסא 2.5 ההתנהגות היתה פחות או יותר כזאת:
כל פעם שהגדרנו קבוע הוא הוגדר בתוך מרחב השמות Object, מה שאומר שכשאתם מגדירים מחלקה בשם ItemsController אתם יכולים להגיע אליה גם דרך השם Object::ItemsController.
כל המחלקות ב Ruby יורשות מ Object, כולל המחלקה Staff שהגדרנו בתוכנית הדוגמא.
לכן, כשאנחנו מנסים לפנות ל Staff::ItemsController, ורובי מגלה ש ItemsController לא מוגדר בתוך Staff, רובי מנסה לחפש את השם הזה בתוך מרחב השמות של ההורים של Staff, כלומר בתוך מרחב השמות Object. ושם הוא נמצא.
ההיגיון העקום הזה הוביל לבאגים מוזרים ב Rails, שטוען באופן אוטומטי מחלקות לפי השם שלהן. בחלק מהמקרים ריילס לא הבין שצריך לטעון קובץ של מחלקה מסוימת, כי הוא מצא כבר את המחלקה במרחב שמות אחר.
הקוד הבא למשל בריילס שבור ועלול להתפוצץ בדיוק בזמן שאתם לא מוכנים לזה:
# models/item.rb
class Item < ApplicationRecord
scope :published, -> { where(published: true) }
end
# models/staff.rb
class Staff < ApplicationRecord
end
# controllers/items_controller.rb
class ItemsController < ApplicationController
def index
@items = Item.published
end
end
# controllers/staff/items_controller.rb
class Staff::ItemsController < ApplicationController
def index
@items = Item.all
end
end
מנגנון הטעינה האוטומטית עלול לחשוב שהוא כבר טען את Staff::ItemsController
רק בגלל שהוא כבר טען את הקלאס Staff
ואת הקלאס ItemsController
.
2. אפשר לכתוב Pipelines טובים יותר עם yield self ו then
הפקודה yield_self
שזמינה עכשיו על כל אוביקט ברובי מעבירה את האוביקט לבלוק ומחזירה את התוצאה. כך היא נראית בדוגמא קצת טיפשית:
> 10.yield_self {|n| n * 4 }
40
האמת ששם קצת יותר טוב לפונקציה הזאת שנכנס ב 2.6 הוא then, אז אפשר לכתוב את אותה הדוגמא כך:
> 10.then {|n| n * 4 }
40
ו then הגיוני יותר כי הוא באמת מרמז על השימוש הנפוץ במנגנון - והוא כתיבת Pipelines קריאים יותר. נתבונן רגע בקוד הבא:
CSV.parse(File.read(File.expand_path("data.csv", __dir__)))
.map { |row| row[1].to_i }
.sum
הקוד מפעיל את expand_path
כדי לחשב שם של תיקיה, את התוצאה מעביר ל File.read
, את התוצאה מעביר ל CSV.parse
ועל האוביקט שיצא מפעיל את הפונקציות map
ו sum
. זה לא קריא כי אנחנו מערבבים כאן שני מנגנונים: גם הפעלת פונקציה על פונקציה על פונקציה, וגם קריאה למתודות של אוביקטים בשרשרת.
בעזרת then
אפשר לעבור רק לכתיב אחד - כתיב האוביקטים. הנה גירסא קריאה יותר של אותו הקוד:
"data.csv"
.then {|name| File.expand_path(name, __dir__) }
.then {|fullname| File.read(fullname) }
.then {|data| CSV.parse(data) }
.map {|row| row[1].to_i }
.sum
בצורה כזאת יש לנו אחידות בשיטת הקריאה והרבה יותר קל להרכיב ולפרק Pipelines או לשנות את סדר הפעולות.
דוגמא נוספת הפעם לגישה ל Github API עשויה להיראות כך:
"https://api.github.com/repos/rails/rails"
.yield_self { |_| URI.parse(_) }
.yield_self { |_| Net::HTTP.get(_) }
.yield_self { |_| JSON.parse(_) }
.yield_self { |_| _.fetch("stargazers_count") }
.yield_self { |_| "Rails has #{_} stargazers" }
.yield_self { |_| puts _ }
ולמרות שיש פה הרבה פעולות אנחנו מקבלים קוד שיחסית קל לקרוא אותו ולעדכן אותו, במה שנראה כמו פעולה אחת ובלי שצריך להגדיר משתני ביניים.
3. אופרטור הרכבת פונקציות חדש
ואם בכל זאת אתם מעדיפים את הכתיב של הפעלת פונקציה על פונקציה על פונקציה, תשמחו לשמוע שגירסא 2.6 של רובי הוסיפה אופרטור חדש להרכבת פונקציות. האופרטור חץ כפול לוקח שתי פונקציות ומחזיר פונקציה חדשה שמפעילה קודם פונקציה אחת ואז מפעילה את הפונקציה השניה על התוצאה. בדוגמא פשוטה זה נראה ככה:
f = -> (n) { n * 2 }
g = -> (n) { n + 1 }
(f << g).(10)
=> 22
ויש גם חץ כפול הפוך בשביל הרכבה בכיוון השני:
(f >> g).(10)
=> 21
המנגנון הזה מאפשר לבנות פונקציות באמצעות שילוב של פונקציות אחרות, למשל הדוגמא הבאה:
require 'net/http'
require 'uri'
require 'json'
fetch =
self.method(:URI) \
>> Net::HTTP.method(:get) \
>> JSON.method(:parse) \
>> -> (response) { response.dig('bpi', 'EUR', 'rate') || '0' } \
>> -> (value) { value.gsub(',', '') } \
>> self.method(:Float)
fetch.call('https://api.coindesk.com/v1/bpi/currentprice.json') # => 3530.6782
4. לא צריך יותר Regexp בשביל למחוק את התחילית או הסיומת
הפקודה chomp של רובי היא בין פקודות המחרוזת האהובות עליי. היא מאפשרת למחוק כל טקסט מסוף מחרוזת ומחזירה את מה שנשאר. כך אם יש לכם שם קובץ תוכלו בקלות לוותר על הסיומת שלו:
'demo.txt'.chomp('.txt')
=> "demo"
אבל מה לגבי התחילית? בשביל זה הייתם צריכים לדעת ביטויים רגולאריים. עד 2.5 כמובן. היום כבר אפשר להשתמש ב delete_prefix
בשביל להיפטר מהתחילית. אגב בשביל האחידות נוספה גם delete_suffix
שבשונה מ chomp לא תמחק לכם באופן אוטומטי את תו ירידת השורה וחייבת לקבל פרמטר. בקוד זה נראה ככה:
> "demo.txt\n".chomp
"demo.txt"
> "demo.txt\n\n".chomp("")
"demo.txt"
> "demo.txt".delete_suffix('.txt')
"demo"
> "demo.txt".delete_prefix('demo.')
"txt"
5. הפונקציה merge מקבלת כמה Hash-ים שתרצו
והפיצ'ר האחרון שאהבתי ברשימה הוא היכולת להעביר ל Hash::merge מספר פרמטרים, מה שאומר שעכשיו אפשר לכתוב:
> {}.merge({a: 10, b: 20}, {c: 30}, {d: 40})
=> {:a=>10, :b=>20, :c=>30, :d=>40}