תגית: ruby

לאן פרל נעלמה אצלי ?

לפני שבוע חבר ביקש ממני סיוע במיון סוגי אימלים שיש לו בלוג (שרת SMTP).
הוא היה צריך לקחת מידע ממספר שורות ולאחד אותם לפי מזהה מסויים שנמצא בלוג (מספר מזהה השליחה), אבל רק כאשר מתקיים משהו מסויים שם. חיפשתי בהתחלה עבורו כלי שאולי קיים לנושא, וכאשר גיליתי שאין ולו אחד כזה, הבנתי שצריך לכתוב אחד.

אז הוא שאל אותי אם אוכל לסייע לו ולהראות לו איך לעשות את זה בפרל, וכאשר הבנתי שאין הגבלה של טכנולוגיה (אלא הוא דיבר על פרל בזכות יכולות הניתוח של השפה), המחשבה הראשונה שלי היתה לכתוב את זה ברובי.

אחרי שסיפקתי לחבר הדגמה על קובץ לוג אחד לדוגמא ששלח לי, הוא אמר לי שהמערכת שלי לא עובדת. מסתבר שהקובץ אצלו הוא עם gzip בגלל ה logrotate. שינוי של 2 שורות בלבד ברובי גרמו לקוד שלי לעבוד. אפילו לא היה צורך להתקין gem מסויים, זה היה פשוט מידי ועבד עם ה stdlib של רובי:

...
require 'zlib'
...
Zlib::GzipReader.open(file).each do |line|
...
end
...

הקוד עצמו לקח לי משהו כמו 40 שורות קוד בגלל שעשיתי את זה בדרך הקריאה, כולל אפשרות לדבג את הנעשה בקוד, ולא בדרך הקצרה לכתיבה (אשר היתה הופכת את זה בערך ל15-20 שורות קוד).

הבחירה של רובי עבור ניהול מערכת אינה נראת להרבה אנשים כמו משהו אפשרי, כי רובם חושבים על רובי כשפה לפיתוח ווב, ולא כשפת פיתוח דינאמית לכל דבר ועניין.
ואם להוסיף חטא על פשע, אז רובי נחשבת בעיני רבים לאיטית מאוד, אבל זו לא השפה אלא אחד המפרשים שלה – MRI, אשר הוא המפרש הראשי, אך לא היחיד לשפה, אך כאשר מהירות אינה חשובה, המפרש הזה של רובי עושה עבודה מצויינת, כל עוד עבודה עם threads אינה חשובה.

אם פעם כאשר היו מבקשים ממני לנתח ככה מידע, אז פרל היה קופץ לי מיד לראש, והייתי מדמיין איך זה נעשה בשפה, כיום לוקח לי זמן לחשוב איך לממש את זה בשפה, בו בזמן שאני יכול לחשוב על מספר דרכים לממש את זה ברובי בלי להשקיע מאמץ.

מדהים להתעורר יום אחד ולגלות שהטכנולוגיות שאתה מחובר ואוהב השתנו ואתה אפילו לא שמת לב לכך.

Sequel חלק שני

בחלק הקודם הצגתי על רגל אחת את הORM שנקרא Sequel והצגה של שימוש בסיסי בו.
בחלק זה, אנסה לתת לכם כמה דגשים על שימוש מעשי יותר בו.

מודל

Sequel תומך באפשרות ליצור מודל -> מחלקה שמייצגת טבלה. אם נחזור למסד הנתונים שיצרתי בחלק הקודם, ניתן ליצור מודל לטבלת Posts:

class Posts < Sequel::Model
end

זה כל מה שאנחנו זקוקים בשביל לגשת אל טבלת Posts. עכשיו התוכנית שלנו מההתחלה עד הסוף תהיה:

require 'rubygems'
require 'sequel'

DB = Sequel::Sqlite('blog.db')

class Posts < Sequel::Model
end

Posts.where('id > :id', 1000).delete

בהתחלה אתחלתי את Sequel, פעולה שלא ניתן לברוח ממנה.
לאחר מכן יצרתי את המודול שלנו (הוא חייב כבר גישה למסד הנתונים).
אחר כך, יצרתי תנאי שאומר להחזיר את כל הרשומות עם id שגדול מ1000, ואז למחוק אותן.
זו הדרך הנכונה ד"א לעשות דברים איתו, כלומר lazy loading של תנאי לפני הפעולה עצמה.

בעת חיפוש, במידה וארצה למשל, לקבל את השדה title, אוכל לעשות זאת כך:

Posts[:title].where(id: 1)

אך במידה וארצה להשפיע על מה יהיה בפעולת ה select, עם יותר משדה אחד, אוכל פשוט להשתמש בפעולה הבאה:

Posts.select(:title).where(id: 1)

יש עוד המון פעולות שמתורגמות למתודות בספרייה, אבל יש לה גם כלי עזר נוספים.
למשל, נגיד ונרצה לבצע pagination למידע.
כלומר נרצה לקבל כל פעם איקס כמות ערכים לדף. ולדעת להמשיך משם.
כאן אנחנו מרגישים את הספרייה יותר, היות ויש לה כלים אפלו לשם כך:

Posts.paged_each(rows_per_fetch: 25) do |row|
  p row
end

מה שקורה כאן הוא שבפועל ריבוי שאילתות המחשבות עבורינו את המידע,אך אנחנו מקבלים רק 25 תוצאות ל"דף".

עוד תמיכה מעניינת, היא לקחת מידע SQL ולקבל כלים שמאפשרים לבצע פעולות של Graph. כלומר מיפוי טבלאות וערכים בצורה של גרף. כלומר שיוך מידע בין טבלאות שונות, כולל האפשרות ליצור alias לטבלאות שונות בשביל להביא את המידע בפועל.

בחלק הבא אדבר על התמיכה בjoin כחלק מתחביר ואסיים עם הסבר על תמיכה בmigration ובשורת הפקודה אשר נקראת sequel.

לימוד שפת רובי

מזה זמן רב שאני רואה כי ישנו מחסור רב במפתחי רובי בשוק, ומצד שני, אני רואה אנשים אשר הולכים לקורס וחצי על Rails ולא מבינים כלום ממה שקורה בשפה.

כבר שמעתי משפטים כמו "עד כמה שהבנתי ברובי אין משתנים, אלא הכל זה מערכים", מאדם אשר עבר קורס בRails.

אז התחלתי לברר האם אנשים מעוניינים ללמוד רובי. הכוונה היא ללמוד את בסיס השפה, להבין מה נגד מה, מי נגד מי, ובעיקר את הלך הרוח והחשיבה של השפה, הרבה לפני השימוש בה בפריימוורקים כדוגמת Rails.

הבעיה שגיליתי היא כי אנשים העוסקים הרבה ברובי בעד הרעיון (אך אינם זקוקים לקורס), בעוד אנשים אשר מחפשים עבודה רוצים רק קורסים על Rails ולא ללמוד את הבסיס להכל.

העניין הוא ששוכחים כי רובי היא לא רק Rails. יש כלים מאוד נפוצים כמו שף ו Puppet, ועוד מספר טכנולוגיות שונות שיש בהם שימוש מעבר לשפה עצמה, אבל משום מה, כולם עשו חיבור כי Ruby = Rails,

וכך יש לנו בעיה שבה יש רצון בשוק למפתחי רובי, אבל נוצרה בעיה כי הרבה מאלו ההולכיות להיות מפתחי רובי, לרוב אינם מכירים או מבינים את השפה, ורק רוצים להיות "ראש קטן" ולהתמודד עם מה שאמור להכניס להם משכורת, אך צעד זה פוגע במעסיק בסופו של דבר, ולכן גם פוגע בביקוש לתכנות ברובי בשורה התחתונה.

ניהול מאגרים מ scm

אני משתמש אצלי ב bundle עבור vim. זהו מנגנון אשר טוען אוטומטית תוספים עבור vim בתוך ספרייה מיוחדת:

$ $HOME/.vim/bundle/

כל התוספים בהם אני משתמש עבור vim, לקוחים מהמאגרים שלהם מscm כלשהו (בעיקר git), וכל פעם מחדש לעדכן ידנית את הכל זו משימה די מעצבנת.
אז החלטתי לכתוב סקריפט לנושא, והיות ואני לא מת על bash שמסוגל לעשות עבודה טובה כאן, החלטתי לכתוב זאת ברובי:

#!/usr/bin/env ruby
#

def update(dir, type) 
  Dir.chdir(dir)
  puts "Going to update #{dir}"
  system("#{type.to_s} pull")
  Dir.chdir('..')
end

scm = [ :git, :hg ]

Dir['*'].each do |f|
  if File.ftype(f) == 'directory'
     scm.each do |s|
       if Dir[f + "/.#{s.to_s}"][0]
         update(f, s)
         break
       end # if Dir[f + "/.#{s.to_s}"][0]
     end # scm.each do |s|
  end # if File.ftype(f) == 'directory'
end # Dir['*'].each do |f|

puts 'done'

הקוד נכתב ב5 דקות וגם נראה ככה, אבל עובד כמו שצריך.
הוא עובר על כל הקבצים במערכת הספריות הנוכחית, ובודק האם הם ספריה. במידה וכן, הוא נכנס פנימה ובודק האם יש בתוכה ספריית scm כלשהי אשר מוכר לי, ובמידה וכן, הוא נכנס פנימה ומעדכן לפי הscm.
הסקריפט חייב לשבת במיקום תחת bundle בשביל לעבוד (ככה תכננתי אותו).

תהנו.

 

לימוד רובי – הדרך להארה

"In Ruby, just like in real life, our world is filled with objects. Everything is an object – integers, characters, text, arrays – everything." — Ruby monk

ישנו אתר מאוד מעניין הנקרא ruby monk אשר מאפשר דרך לימוד אינטרקטיבית עבור שפת התכנות רובי (בטח לא ניחשתם את זה … 🙂 )

האתר מלמד בדרך פחות קונבנציונאלית ממה שנהוג בד"כ, וכתוב בסגנון העושה חיקוי של נזירים בודהיסטים, ולדעתי גם מעט מצחיקה.

העניין הוא, שלא רק שיטת הלימוד שונה, אלא גם יש מספר "ספרים" (כותרות) שניתן ללמוד. יש רמות לימוד שונות, אשר מתבססות על הספרים הקודמים וכיוב' …

הלימוד הבסיסי הוא בחינם, ומכיל 2 ספרים. ישנו גם לימוד מתקדם, אשר הוא בתשלום של 10 דולר לחודש (נכון לכתיבת הפוסט), ומאפשר ללמוד מידע מתקדם יותר, כדוגמת הבנה של metaprogramming באמצעות 2 ספרים נוספים (נכון לכתיבת הפוסט).

אתם מוזמנים להציץ בתוכן הלימוד של הספר הראשון, ולהחליט האם הדרך הזו מושכת אתכם.

שימוש ב DATA של רובי

רובי מאפשרת ליצור איזור מיוחד בסוף קובץ ריצה.

האיזור נקרא __END__ והוא מאפשר לשים טקסט חופשי שם, בלי שהוא ישפיע על הקוד (אלא אם הוא קידוד שפה שונה משאר המידע בקובץ).

אפשר אבל מהקוד עצמו לקרוא את המידע הזה, ולעשות עליו פעולות:

#!/usr/bin/env/ruby
#

DATA.each_line do |l|
  puts l
end

__END__
hello
world

תוכנית זו תציג כל שורה תחת האיזור. DATA הוא קבוע (constant), אשר מחזיק בתוכו מחלקה מסוג File, כך שניתן להשתמש במידע ממש כמו כל סוג IO אחר.
למשל במידה ונרצה לשים template של html אשר יהיה מוטבע בקוד, זו הדרך.
כך תוכלו ליצור למשל ספרייה המציגה הודעת שגיאה ב html ללא צורך לפרוס גם קבצים נוספים.

והנה דרך להציג את התוכנית הקודמת בתצורה אוקטאלית (מבוסס על הקוד מכאן):

#!/usr/bin/env ruby
#

if __FILE__ == $0
  offset = 0
  while (buf = DATA.read(16)) do
    bytes = buf.unpack 'C*'
    puts "%08X: %s\n" % [ offset, bytes.map { |b| " %04o" % b }.join('') ]
    offset += 16
  end
end

__END__
0000000 2123 752f 7273 622f 6e69 652f 766e 7220
0000020 6275 0a79 0a23 440a 5441 2e41 6165 6863
0000040 6c5f 6e69 2065 6f64 7c20 7c6c 200a 7020
0000060 7475 2073 0a6c 6e65 0a64 5f0a 455f 444e
0000100 5f5f 680a 6c65 6f6c 770a 726f 646c 000a
0000117

IPC באמצעות Redis ורובי

Redis הוא מסד נתונים אשר אינו מבוסס SQL וכתבתי עליו רבות כאן בעבר.

לאחרונה נדרשתי לפרק מערכת שלי למספר תתי מערכות, ולהעביר מידע בניהם. כמובן שיש הרבה מאוד דרכים לעשות זאת, כדוגמת Message Queue ‏(RabitMQ, ZeroMQ וכו'), עבודה עם PIPES ביוניקס, עבודה בתצורת client server ועוד הרבה מאוד דרכים כיד הדמיון והמערכת הטובה לכם. אני החלטתי ללכת על Redis.

הסיבה לכך פשוטה: אני צריך לבצע פעולה, ואחרי שתפסתי את האירוע, המידע לא צריך להישמר בזיכרון. יותר מזה, אם וכאשר יש הפסקת חשמל, אתחול או אפילו איבוד מידע, אזי אין לי צורך לבצע שוב את הפעולה. כלומר היא נכונה רק לשלב שבו היא נשלחה.

דבר נוסף, הוא שהמערכות שלי כתובות ברובי, ורק מה שצריך לקבל כאירוע לא נכתב, וRedis גם ככה כבר מותקן, ואני כבר מכיר אותו, ויש לי מעט מאוד זמן לכתוב את השינויים.

לRedis יש מספר דרכים לשלוח ולקבל אירועים, אבל אני הולך לדבר כאן על Publish ו PSubscribe. האחרון מאפשר להאזין להודעות באמצעות תבנית חיפוש (עם כוכביות), במקום שם מלא ומדוייק שאחיו Subscribe מספק.

לשם כך צריך לפחות 2 פרוססים שונים, האחד שולח הודעות, והשני מקבל.

שליחת הודעות מאוד פשוטה:

require 'rubygems'
require 'redis'

redis = Redis.new(:host => 'localhost')

10.times do |i|
  redis.publish("number_#{i + 1}", Marshal.dump({:number => i}))
  sleep( (i + 1) / 10.0)
end

התחברתי לRedis, והתחלתי לרוץ על כל המספרים מ0 ועד 9.
יצרתי מפתח שההתחלה שלו הוא number_‎ ולאחר מכן המספר. המבנה חשוב מאוד בשביל להצליח להאזין לאירוע.
לאחר מכן, יצרתי תצורה בינארית עבור Ruby Hash ושלחתי אותה כמידע שאני זקוק לו לשימוש.
כאשר הפעולה מסתיימת, אני נח כמות של מילישניות לפי המספר, החישוב לדעתי די ברור כאן.

הפרוסס השני צריך להקשיב לאירוע:

require 'rubygems'
require 'redis
redis = Redis.new(:host => 'localhost')

redis.psubscribe('number_*') do |on|
  on.pmessage do |pattern, event, message|
    puts "pattern: #{pattern}, event: #{event}, message: #{Marshal.load(message)}"
  end
end

כאן אנחנו מקשיבים לפעולות אשר נשלחות עם סוגי publish השונים.
יש האזנה לאירועים באמצעות תבנית (pattern) ומכאן המקדם בעצם של הפקודה (p).
הפקודה בעצם מכניסה אותנו לתוך בלוק אשר דרכו אפשר להאזין למגוון סוגים של אירועים.
כן האירוע שצריך הוא pmessage לו אני מאזין, וכאשר הוא מתרחש, הקוד בתוכו מורץ.
אני משחזר כאן את המידע הבינארי חזרה למחלקות של רובי, ובכך העברתי מידע סיריאלי לחלוטין של רובי.
חשוב להבין כי זוהי פעולה שהיא blocking, ולכן כאשר מריצים אותה, אין המשך כלפי מטה לקוד.

התוצר כאן יהיה בסגנון הבא:

pattern: number_*, event: number_1, message: {:number=>0}
pattern: number_*, event: number_2, message: {:number=>1}
pattern: number_*, event: number_3, message: {:number=>2}
pattern: number_*, event: number_4, message: {:number=>3}
pattern: number_*, event: number_5, message: {:number=>4}
pattern: number_*, event: number_6, message: {:number=>5}
pattern: number_*, event: number_7, message: {:number=>6}
pattern: number_*, event: number_8, message: {:number=>7}
pattern: number_*, event: number_9, message: {:number=>8}
pattern: number_*, event: number_10, message: {:number=>9}

טעינת קובץ csv ברובי

אם יש לכם צורך לייבא מידע מקובץ csv, זה יכול להיות כאב ראש. בייחוד אם עובדים מול אנשי ווינדוז החושבים כי אקסל זה כלי יעיל (בהשוואה לLibreOffice Calc).

רובי מביא עימו כחלק מהספריות הסטנדרטיות שלו, ספרייה בשם csv. למראית עין היא פשוטה מידי ולא תומכת בכלום, אבל למעשה יש בה המון יכולות, אבל צריך לדעת ולהבין כיצד לעבוד עם הספרייה. לדעתי האישית, הAPI שלה בעייתי מעט בגישה שלו, אבל בכל זאת הספרייה שימושית מאוד.

הטעינה הפשוטה ביותר היא כזו:

require 'csv'
CSV.foreach('path/to/file.csv') do |row|
  # use row here...
end

הערך ש row מכיל, הוא מערך של טקסט אשר היה מופרד בפסיקים. אבל במידה והוא אינו מופרד בפסיקים, יש בעיה.
לשם כך, אפשר להוסיף עוד פרמטר שהוא בעצם hash ומאפשר לנו להגדיר את המפריד: להמשיך לקרוא

הרצת מתודות בצורה דינאמית חלק שני

בחלק הקודם, דיברתי על הרצת מתודות בשפת פסקל מונחת עצמים. בחלק הזה אדבר על הרצת מתודות בשפת רובי.

לרובי יש שלוש צורות שאני מכיר, על מנת להריץ מתודות בצורה דינאמית. הדרך הראשונה היא שימוש ב eval.
eval מאפשרת להריץ תוכן של מחרוזת המועברת למתודה. המחרוזת היא קוד רובי.

s = 'Hello World'
eval "puts s"

התוכן שיודפס הוא "Hello World", היות והמשתנה s הוגדר כבר למערכת.

הדרך השניה שיש ברובי, היא דרך להכניס מתודות למשתנה, ולהריץ אותן בזמן "אחר":

myproc = method(:puts)
myproc.call 'Hello World'

math = 1.method('+')
puts math.call(1) #prints 2

מה שקורה הוא, שֵהמתודה method מחזירה מחלקה בשם Method עם המתודה שרוצים להריץ, וקריאה ל call, מריצה אותה. כאשר יש צורך בפרמטרים, אז מעבירים ל call את הפרמטרים עצמם.

הדרך האחרונה  להריץ מתודה בצורה דינאמית, היא על ידי שימוש במתודה send:

puts String.send('to_s') # prints 'String'
puts 'Hello World'.send('[]', 1) # prints 'H'

תכנות דינאמי נעים 🙂

CentOS Packages

I find myself once in a while with a need for software package that does not exists for CentOS (or at least not the version I need them to be), so I find myself maintaining some spec files (and sometimes even more) of the required software.

I started adding such packages to GitHub, in order also to help others if they also find themselves with such a need.

At the moment I have 2 packages:

In the feature I might add additional packages, and also merge them into one big tree

You are more then welcome to fork me at Github 🙂