קטגוריה: Ruby

גילוי פורטים פתוחים בצורה נאיבית

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

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

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

  1. התעבורה עברה, אבל לא קיבלנו אישור – הפורט פתוח אבל מפולטר (מלווה ב Connection Refused).
  2. אין פורט פתוח – אולי מפולטר, ואם כן מקבל drop כאילו אין פורט פתוח.

פעולה זו של SYNchronize היא הדרך עבורינו לגלות האם TCP פתוח.

ההצעה הפשוטה ביותר להתמודד עם זה, זה לנסות לפתוח חיבור TCP בפורט המבוקש לכתובת, במידה ונפתח, החיבור פתוח. במידה והחיבור נכשל (אם קיבלנו timeout או connection refused), אנחנו יודעים שהוא לא פתוח. כאשר האחרון אומר כי הוא מפולטר.

זו לא הדרך היחידה לעשות את זה, אבל זו הדרך הפשוטה ביותר.

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

רובי:

#!/usr/bin/env ruby
require 'socket'
def tcp_connect(address, port, timeout: 20)
# making sure we are talking with IP
connected = false
addr = Socket.getaddrinfo(address, nil)
sock_addr = Socket.pack_sockaddr_in(port, addr[0][3])
Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
begin
socket.connect_nonblock(sock_addr)
rescue IO::WaitWritable
if IO.select(nil, [socket], nil, timeout)
begin
# try again, might work
socket.connect_nonblock(sock_addr)
connected = :connected # no other exception, then should be yes
rescue Errno::EISCONN # we have a connection
connected = :connected
rescue Errno::ECONNREFUSED # we are filtered
connected = :filtered
rescue Errno::ETIMEDOUT # timeout
connected = :closed
rescue StandardError # something else :'(
connected = :error
end
else # unable to wait for an answer, but no exception was raised
connected = :error
end
rescue StandardError # ops, something went wrong
connected = false
ensure
socket.close
end
end
connected
end
puts tcp_connect('192.168.97.3', 8823)
view raw scan.rb hosted with ❤ by GitHub

גו:

package main
import (
"fmt"
"net"
"strings"
"time"
)
const (
errUnknown = "Unknown error"
connTimeout = "closed"
connFiltered = "filtered"
resolveError = "Resolve error"
lookupError = "Lookup error"
connSuccess = "open"
)
func tcpConnect(address string, port int, timeout time.Duration) (string, error) {
ipList, err := net.LookupHost(address)
if err != nil {
return lookupError, err
}
if len(ipList) == 0 {
return resolveError, fmt.Errorf("Unable to resolve %s", address)
}
connAddr := fmt.Sprintf("%s:%d", ipList[0], port)
conn, err := net.DialTimeout("tcp", connAddr, timeout)
if err != nil {
sErr := err.Error()
if strings.HasSuffix(sErr, "connection refused") {
return connFiltered, err
}
if strings.HasSuffix(sErr, "i/o timeout") {
return connTimeout, err
}
return errUnknown, err
}
defer conn.Close()
return connSuccess, nil
}
func main() {
addr := "google.com"
port := 443
status, err := tcpConnect(addr, port, time.Duration(3)*time.Second)
fmt.Printf("%s:%d - ", addr, port)
if status == errUnknown {
fmt.Println(err)
} else {
fmt.Println(status)
}
}
view raw scan.go hosted with ❤ by GitHub

פיתון למתכנת רובי

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

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

אז מה לא אינטואיטיבי עבורי?  הכל 🙂
סתם, הנה הדגמה קטנה לרובי:

להמשיך לקרוא

vim, neovim ו javascript

אני נחשב למתכנת מסוג full stack מסתבר, ככה לפחות הגדירו אותי אחרים.
אני מוצא את עצמי נמצא רוב הזמן כאשר אני מתכנת לפחות, משתמש בvim, כל עוד לפחות, לא מדובר בשפת Javascript, על שלל ספריותיה, כדוגמת React או Vue אשר איתן אני עובד לרוב.

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

כאשר אני מתכנת בשפות כדוגמת Golang, רובי או פיתון, יש לי כלים ממש איכותיים לנושא עבור vim, גם כאשר אני נוגע למשל בתסריטי teraform, אני עדיין מקבל תמיכה יחסית טובה, אבל כאשר מדובר ב JS, זו כבר בעיה. אני חושב שהסיבות הן שיש הרבה תקנים שהם עדיין במצב של offer ו draft וככה es6 וכיוב', יוצרים אתגרים מעניינים.

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

לאחרונה אפילו עברתי ל neovim, אשר דווקא עושה עבודה מדהימה, עם כמה בעיות ממש קטנות, הוא מספק לי כלי מעט טוב יותר מvim עצמו, הוא אפילו מכיל כמה כלים שלפעמים נדמה ש tmux קצת מיותר (למרות שאני עדיין לא משתמש בו), למשל היכולת לקבל מסוף מובנה עם ‎:terminal אשר עדיין מקבל תוכנות של vim בתוכו.

אני כבר הרבה זמן מנסה לגרום לvim לעבוד עם Javascript, והתחלתי לקרוא מאמרים שונים, כדוגמת המאמר הבא, אשר מסביר קצת יותר לעומק איך להגדיר את JS, והנה אני כבר בשלבים להתחיל לזנוח את Atom, ולגרום ל neovim להיות עורך הטקסט כמעט היחידי שלי.

 

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

מימוש RDP טבעי בשפת רובי

על רגל אחת, RDP הם ראשי תיבות של Remote Desktop Protocol. אך השם מעט מטעה. זוהי למעשה אסופה של הרבה מאוד פרוטוקולים, חלקם אפילו לא של מיקרוסופט, אלא של ארגון ITU. למעשה הוא מבוסס על סדרה של פרוטוקולים שהחלו את דרכם בארגון ה ITU אשר נועדו לאפשר לתוכנות לשתף מידע ותכנים בניהם.

הפרוטוקולים עצמם הם פרוטוקולים בינאריים, ולא פרוטוקולים טקסטואליים (כדוגמת HTTP למשל), וככאלו יש עבודה רבה עם bit manipulation והבנה של מיקומים שונים ומה המשמעות שלהם.
הרעיון של RDP הוא להצליח להעביר את כל סביבת המיקרוסופט למחשב מרוחק, כולל העתקת קבצים, שיתוף מדפסות, שיתוף כוננים, שיתוף קול ואפילו לבצע הזרמה של ווידאו.

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

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

כל מי שהנושא מעניין אותו, מוזמן כמובן לגשת לפרויקט, וכמובן שאשמח גם לקבל תרומות קוד מאחרים 🙂

כתובת הפרויקט: https://github.com/Safe-T/rdp-rb

פיתון מול רובי, לא מה שחשבתם

הקדמה

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

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

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

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

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

שאלה ראשונה

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

a = 10.times.select{ |x| x % 2 == 0 }
a
=> [0, 2, 4, 6, 8]

זהו, זה כל מה שצריך, שורה אחת 🙂
אסביר אותה בקצרה. ברובי כל דבר הוא אובייקט, אין כזה דבר משהו שהוא "פרימיטיבי" (בהשוואה לג'אווה למשל).
מספר שלם "רגיל", הוא אובייקט מסוג Fixnum. יש לו מתודה אשר מקבלת ירושה ממחלקת האב – Integer בשם times, אשר מבצעת כמות הפעמים של המספר המבוקש איטרציה.
במידה ולא יוצרים block או proc בשביל לרוץ כמניית האיטרציה, חוזרת לנו מחלקה מסוג Enumerator אשר היא מחלקת בסיס לדברים בעלי יכולת מנייה (כדוגמת מערכים). במחלקת ה Enumerator ישנה מתודה בשם select, המאפשרת להגדיר אילו ערכים באיטרציה כלשהי יחזרו אלי כמערך, כאשר אני יצרתי כאן למעשה proc עם הוראות.
היות וברובי השורה האחרונה בהכרח תמיד חוזרת (אם אין explicit return), אז הוא יודע שכאשר יש מספר זוגי, אז אני רוצה שזה הערך אשר יחזור אלי.
עכשיו הנה משהו מעניין על רובי תוך כדי ההסבר – אין ברובי אופרטורים. כל מה שנראה כאופרטור (כדוגמת חילוק השארית ופעולת השוויון), הם למעשה מתודות, ולכן מתודה כדוגמת even?‎ זהה למה שהייתי צריך, ויתר מזה, אין צורך בסוגריים, ולכן זה יכול גם להראות כך:

a = 10.times.select{ |x| x.even? }
a
=> [0, 2, 4, 6, 8]

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

שאלה ראשונה – תת שאלה

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

a = []
10.times do |i|
  a.push(i) if i.even?
  puts a.inspect
end

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

ובנוסף, לרובי גם יש תמיכה בשני תחבירים של משפט טרינארי אשר שם נפל השואל:

i = 1
a = if i.even? then 't' else 'f' end
b = i.even? ? 't' : 'f'

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

c = unless i.even? then 'f' else 't' end

שאלה שניה

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

a = [1, 2, ['a', 'b', [0xa, 0xb] ]]
=> [1, 2, ["a", "b", [10, 11]]]
a.flatten
=> [1, 2, "a", "b", 10, 11]

קצת הארכתי את הקוד, אשר יכול להיות בשורה אחת בלבד, אך רציתי להדגים את הערך שיחזור אלי.

כפי שכתבתי בתשובה לשאלה הראשונה, מערך ברובי, יורש מ Enumerable, אבל יש לו גם הרבה מתודות משל עצמו.
מתודה אחת כזו היא flatten, אשר עושה בדיוק את מה שאותו בחור ביקש.
המתודה יכולה להגיע גם בעוד צורה בשם flatten!‎ אשר התפקיד שלה הוא זהה, עם שינוי קטן אחד – היא לא מחזירה מערך חדש בלי לגעת בקיים (כפי ש flatten עושה), אלא היא משנה את המערך הקיים וחוזרת עם אובייקט המערך עצמו.

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

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

שאלה שלישית

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

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

str1 = 'yoyo'
print 'sdfsdfsfsdfsfsdf' if str1.eql?('aaaaa') || str1 == 'yopo' || str1 === 'yoyo'

ישנם כאמור עוד דרכים לביצוע השוואת מחרוזות, אבל אלו מספיקות, לא?
על רגל אחת (בלי באמת למצות את ההסבר עד הסוף שלדעתי דורש פוסט שלם רק על זה):
המתודה (בדיקה) הראשונה של eql?‎ בודקת האם האורך והתוכן זהים.
המתודה (הבדיקה) השנייה של ==, בודקת האם זה תוכן זה הוא מסוג מחרוזת, ובמידה וכן, בודקת אורך ורק אז את התוכן.
המתודה (הבדיקה) השלישית של ===, בודקת גם היא את סוג המחלקה, ובמידה ושניהם מחרוזות אז מבצעים פעולה הזהה ל==, במידה ולא, תהה בדיקה האם יש מכנה משוטף בניהם הניתן להשוואה באמצעות פעולת case.

שאלה רביעית

בשאלה הרביעית, רצו להוריד קובץ מאתר מאובטח.
הנה משהו פשוט שמוריד תמונה מגוגל בגישה מאובטחת:

require 'open-uri'

image = open('https://www.google.com/images/srpr/logo11w.png')
open('/tmp/image.png', 'wb+') { |a| a.write(image.read) }
image.close

קצת פירקתי את הדברים לקריאות.
אני טוען את הספרייה open-uri, אשר תפקידה הוא לדעת לפתוח קישורים של ftp, http ו https כאילו היו file descriptor של רובי.

אני במקרה הזה ניגש לקובץ תמונה של גוגל, ושומר אותו כקובץ בינארי בספריית /tmp/ .
שימו לב כי אינני סוגר את הקובץ תמונה שאני שומר בדיסק, והוא נסגר לבד, בזכות פעולת ה proc, אשר בעצם מתבצעת "מאחורי הקלעים" באמצעות yield. וכאשר פקודה זו מסתיימת, הפונקציה של open סוגרת את עצמה לבד.

סיכום

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

recursive tail

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

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

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

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

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

הגישה המוכרת ביותר לכך נקראת tail recursion. או רקורסיית זנב בעברית טכנית.

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

מה הכוונה? אשתמש בקוד רובי (שהוא לדעתי קריא מאוד) לשם כך.

רקורסיה בגישה ה"רגילה" תהיה כתובה כך:

def recursive(n)
  if n <= 1
    1
   else
     n * recursive(n - 1)
   end
end

recursive(4)
=> 24

רקורסיית זנב, תראה כך:

def tail_recursive(current, n, result)
  return result if current <= n

  new_result = result * current
  tail_recursive(current + 1, n, new_result)
end

tail_recursive(1, 4, 1)
=> 24

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

אבל לא הכל ורוד בזה. קשה יותר לדבג בעיות ברקורסיית זנב, היות ולמעשה אין לנו stack frames רבים, שיהיה ניתן להבין באיזה שלב יש בעיה. ובכך אין לנו stack trace שיסייע לנו בנושא.

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

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

סינטרה מודולרית

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

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

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

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

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

אנחנו משתמשים בRack בעצם, היות וכמעט וכל הframeworks עבור בניית מערכות web ברובי משתמשים בו, אנו זקוקים לקובץ קבוע בשם config.ru.

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

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

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

כל הקסם של ניהול מספר מחלקות, נעוץ אצלי ב app.rb.
אני יצרתי אותו שיהיה מאוד פשוט – טען לי את המחלקות האחרון והכנס אותן לסביבת העבודה של רובי, על ידי שימוש ב use.

למערכת שיצרתי ישנם שני מצבים – מערכת לדיבוג REST, ומערכת "בדיקות" אשר עליה אפשר לבדוק שדברים עובדים, והיא בעיקר על תקן "echo" כלשהו.

את המערכת של יצירת המסכים, ושליחת ה REST, יצרתי בקובץ rest.rb, והכל מאוגד שם.
יש שם משהו שהולך לעבור למקום אחר בקרוב, וזה מספר מתודות לסיוע בפעולות.

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

Sinatra תומך במשהו אשר קיבל את השם Helpers, וזה מתודות אשר מסייעות לנו לבצע דברים, בצורה שלא נהיה צריכים לחזור עליה כל פעם, וזה זמין גם ל view שלנו, ובגרסה הבאה שאשחרר (נכון לכתיבת הפוסט), המידע יעבור לקובץ בשם helpers.rb ואיתו עובדים קצת שונה ברובי.

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

במקרה הזה, הגדרתי כי במצב של ‎:development משתמשים ב Sinatra::Reloader, אשר מגיע עם Sinatra-Contrib – תת פרוייקט המספק הרבה כלי עזר לדברים שונים.
הסיבה לשימוש ב Reloader הוא לא לאתחל את השרת בכל שינוי שעושים למחלקה של סינטרה, כאשר Reloader מגלה כי התוכן של הקובץ השתנה, הוא גורם ל rack לטעון אותו שוב, וככה אנחנו לא זקוקים לטעינה מחודשת של השרת עצמו.

המערכת שכתבתי, משתמשת ב template בשם haml, למעשה פעם ראשונה אשר אני משתמש בה מרצון. תוכלו למצוא את ה layout.haml שהוא המסגרת הרגילה וכן כרגע קובץ בשם index.haml תחת ספריית view.
ועבור העיצוב, אני משתמש ב Foundation 5, אשר אני אוהב אותה יותר מאשר bootstrap.
עבור Javascript יש גם את jQuery וגם את knockout.js, כאשר אני נעזר גם ב lodash.js למספר דברים פשוטים, והיא מספקת בעצם גרסה שעברה אופטימיזציה ל underscore.

את הקבצים של Foundation, וכל ה Javascript ניתן למצוא תחת public.

דבר אחרון שנשאר לספר עליו הוא שאני משתמש במשהו אשר נקרא puma.
מה זה ?
puma הוא משהו שלוקח את rack וגורם לו להיות שרת לכל דבר ועניין, אשר ניתן לבצע עליו חיבור לשרתי HTTP שונים, כדוגמץ apache או nginx.
החיבור נעשה על ידי הגדרת proxy בשרתים.

ההגדרות של puma, נמצאות תחת config, וכפי שניתן לראות את הקובץ הראשי והחשוב, הוא גם יודע לבנות לעצמו מבנה ספריות במידה והן לא קיימות, דבר שהוספתי לקוד עצמו. הוא כרגע מכוון למצב של development, ולכן יש לשנות קצת הגדרות בשביל שזה יעבור למצב production.

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

שינוי פורמט לוג של Rack, והסתרת אזהרות ברובי

יש לי מערכת העובדת עם Sinatra, unicorn ו nginx ביחד.

הבעיה היא, ש nginx נמצא בראש, והוא מעביר את הבקשה ל unicorn, אני מקבל את כתובת ה ip בלוג של ה nginx ולא של הפונה המקורי, ורציתי לשנות את זה, שאראה את הכתובת של הפונה, ולא של nginx.

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

# ... 
# snips 
# ...

# Overriding the original constant
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}

# ... 
# snips 
# ...

def log(env, status, header, began_at)
  now = Time.now
  length = extract_content_length(header)

  logger = @logger || env['rack.errors']
  logger.write FORMAT % [
    # adding IP
    env['HTTP_X_REAL_IP'] || env['HTTP_X_FORWARDED_FOR'] || 
    env["REMOTE_ADDR"] || "-",
    env["REMOTE_USER"] || "-",
    now.strftime("%d/%m/%Y %H:%M:%S"),
    env["REQUEST_METHOD"],
    env["PATH_INFO"],
    env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
    env["HTTP_VERSION"],
    status.to_s[0..3],
    length,
    now - began_at 
  ]
end

# ...
# snips 
# ...

רוב הקוד לא מעניין, כי זה בעצם להשתמש בקוד קיים, אבל log מעניין אותנו, ולכן אני מציג רק אותו.
ברירת המחדל אומרת לספק את REMOTE_ADDR, אבל זו הכתובת של nginx.
אז ב nginx יצרתי הדר שאני מעביר את הפניה המקורית. השם שלו קיבל את השם HTTP_X_REAL_IP. ככה בחרתי.
יצרתי אותו בצורה הבאה:

proxy_set_header        X-Real-IP       $remote_addr;

במידה והוא אינו קיים, אני מנסה לבדוק האם HTTP_X_FORWARDED_FOR קיים, במידה ולא, אז אנסה את מזלי עם REMOTE_IP, ואם זה גם לא, אז אין IP.

במידה ותריצו את זה, תגלו אבל בעיה חדשה – הודעת אזהרה ש FORMAT כבר הוגדר:

warning: already initialized constant Rack::CommonLogger::FORMAT
/home/ik/.gem/ruby/2.0.0/gems/rack-1.5.2/lib/rack/commonlogger.rb:24: warning: previous definition of FORMAT was here

אז מצאתי להודעה הזו פתרון נחמד ופשוט:

module Kernel
  def suppress_warnings
    original_verbosity = $VERBOSE
    $VERBOSE = nil
    result = yield
    $VERBOSE = original_verbosity
    return result
  end
end

והשימוש בו יהיה בצורה הבאה:

module Rack
  class CommonLogger
    Kernel::suppress_warnings do
      # Overriding the original constant
      FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
    end

    def initialize(app, logger=nil)
...

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

הקוד המלא נמצא כאן.

גו – השלמתה של שפה

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

למשל יכול להיות שרת ג'ינטו מ 2007 (שלא התעדכנה) ומערכת פדורה 20, שהמערכת צריכה לרוץ על שניהם.

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

הסיבה שלי של לא לקחת את C או ++C היא די פשוטה – C נכתבה עבור קרנלים, וכל שימוש בה מעבר הוא כאב ראש, אשר לא מצדיק את השימוש בה, אם לוקחים שפות אחרות המספקות את אותה התוצאה.
ו ++C זו מפלצת, שלדעתי אין לה באמת שימוש אמיתי (אבל מה אני מבין).

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

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

צד השרת (שלי) נשאר ברובי, עם grape עבור הקליינט בGo, ועבור המשתמש הרגיל, יש סינטרה (אין יותר מידי ווב שם עבור הלקוח).

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

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

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

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

ענב הזעם – API

ברובי, הדבר הכי מוכר ביקום הוא framework בשם Rails, אשר מאפשר ליצור אתרים דינאמיים בצד השרת.

ישנם עוד מספר סוגי framework עבור עולם ה web, בהם Sinatra, שאני משתמש המון, ולאחרונה התחלתי לשחק גם עם framework מאוד מעניין שנקרא grape.

Grape הוא framework אשר נועד לתת לנו כלי לפיתוח API. זה אומר שכל הפיטצ'רים שלו הם על טהרת REST ואין שום דבר הקשור להצגת templates למשל בצד המשתמש.

כלומר ניתן להציג xml, json או כל מבנה אחר של מידע, אבל זה לא נועד ליצור אתר, אלא להחזיר מידע, או לבצע פעולות API.

הנה הדגמה קלה כיצד תראה תוכנית שכזו: להמשיך לקרוא

סידור תיבות דואר

יש לי חבר קיבל פרויקט בעבודה שלו – סידור מחדש של שרתי דואר של לקוחות החברה (חברת ISP), ולמרות שהם כולם בתוצרת maildir, הם מסודרים בצורה לא קבועה, וחלקם נמצאים בספרייה בשם mbox למשל, למרות שהם לא בפורמט הזה.

החבר ביקש ממני לסייע לו להעתיק את המידע מהשרת הישן לשרת חדש, אבל הוא לא ידע איך לסדר את הבלאגן לצורה מסודרת יותר.

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

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

  • חיפוש תתי ספריות ברקורסיה
  • איטרציה על קבצים
  • ביצוע פילטר על תוכן במערך
  • יצירת ספריות
  • העתקת תוכן של ספריות בצורה רקורסיבית

עוד מערכת לדיווח על אזעקות

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

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

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

אם מעניין אתכם לראות את הגרסה הכי אחרונה,גם אם תעודכן על ידי, תמצאו אותה כאן.

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

ראיתי כאלו אשר לקחו מפיקוד העורף את רשימת האיזורים ומשתמשים רק בקוד, אך שמתי לב בזכות זה שאני שומר לקובץ את המידע, שלפעמים הם שוברים אפילו את רשימת האיזורים, למשל איזור 209 (לצורך הדגמה) פתאום מגיע כ09 במידע שלהם ואז חוזר להיות ,209 ולכן החלטתי לא לבנות משהו שיתרגם את זה, אלא לשמור את הדיווח כפי שמתקבל.

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

 

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

מערכת פשוטה לשיתוף קבצים פנים ארגונית

יש לי פרויקט ארוך טווח עם מספר אנשים, אשר חלקם מאוד לא טכניים, בלשון עדינה.
היה צורך לשתף בין כולם קבצים, אבל כל אחד עובד על סביבות לגמרי שונות, וכל האנשים הם עסקים קטנים מאוד, אשר מערכות שיתוף קבצים רגילות כדוגמת share point או own cloud הם overkill רציני לנושא, ולפחות המערכת של מיקרוסופט יקרה מידי לנושא, ומחייבת אותנו להחזיק שרתי ווינדוז ללא סיבה אמיתית.

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

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

אז החלטתי כי במקום להמשיך ולחפש תוכנה, אכתוב משהו פשוט שיעשה את זה, ותוך שעה וחצי, כולל דיבוג כתבתי את simple file sharing – גרסת mercurial וגרסת git.
זהו מנוע פשוט, הכתוב ברובי עם סינטרה ללא javascript שאני כתבתי, אלא cgi מול html/css פשוטים מאוד מבוססי foundation.

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

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

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

שחרור משאבים ברובי ו Go

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

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

כיצד זה מגיע לידי ביטוי ?
הדגמה בGo:

func open_file {
f, err := os.Open("/tmp/a_file")
if err != nil {
log.Fatal("Unable to open a_file")
}
defer f.Close()
// do something here
}
view raw defer.go hosted with ❤ by GitHub

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

ברובי קוד שכזה יראה כך:


def read_file
  f = open('/tmp/a_file')
  # do something here
rescue => e # catch exceptions globally
   $stderr.puts("Unable to open a_file") if e.kind_of? Errno::ENOENT
ensure
  f.close if f
end

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

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

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

Sequel וPostgreSQL

לאחרונה אני מוצא את עצמי משתמש בתכונות מאוד מתקדמות של PostgreSQL, שחלקם נוספו רק ב9.2 (למשל range). העניין הוא שאני עובד עם רובי ו Sequel, ולרוב אנחנו שמים לב כי ORM אינם אוהבים דברים שהם לא סטנדרטיים.

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

ישנם שני דרכים לטעון תוספים:

  1. דרך גלובלית
  2. דרך שתופעל רק על החיבור שלנו

הדרך הגלובלית נראת כך:

Sequel.extension(:core_extensions, :pg_range, :pg_array)

הדרך של החיבור, נראת כך:

DB.extension(:pg_range, :pg_array)

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

DB[:readers].where(Sequel.pg_range(:comments_id).contains([10, 15, 1900])).sql
# SELECT * FROM "readers" WHERE ("comments_id" @> (10, 15, 1900))

והנה יש לנו שאילתא אשר משתמשת ב Range של Pg, אבל כתובה עם ORM.
בתעוד כתוב שגם שם השדה יכול להיות המקור של pg_range (למשל), אבל אצלי זה לא כך (גם אם זה גלובלי).

למערכים, הפעולה מאוד דומה גם כן:

DB[:comments].where(uid: 10).where(Sequel.pg_array_op(:comments).contains(Sequel.pg_array([15,23]))).sql
# SELECT * FROM "comments" WHERE (("id" = 1) AND ("comments" @> ARRAY[15,23]))

קריאה נוספת:

רובי לאן ?

ב 2006, ראיתי קוד של Metasploit שעבר מפרל לרובי, והיה לי קל מידי להבין אותו, אז החלטתי ללמוד את שפת התכנות.

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

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

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

בשל כך, אני שומע הרבה מאוד אנשים שמספידים את השפה. למעשה אני כל הזמן שומע על חוסר ההפרדה בין רובי לבי Rails, אבל כפי ש Metasploit כתוב בשפה, כך גם עוד הרבה מאוד כלים אחרים (כדוגמת fog, puppet, chef, capistrano ועוד).
כפי שכבר כתבתי בעבר, אפילו אצלי, היא מחליפה כבר מזמן את פרל (שגם את השפה הזו אני מאוד אוהב). אפילו YaST – הכלי הכי מזוהה עם סוזה, שוכתב לרובי.

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

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

מראה מראה שעל הקיר – planet foss il

הרבה זמן שיש בעיות עם האגריגיטור של planet.foss.org.il. לפעמים הוא למעלה ולפעמים הוא למטה.
אז החלטתי לקום ולעשות מעשה – ליצור אתר מראה עבורו בכתובת: planet.linesip.co.il.

אין לאתר שום רצון להתחרות ב planet.foss.il אלא לשמש מראה שתאפשר עדין לראות בלוגים גם אם האתר אינו מגיב.

הדרך שביצעתי את המראה פעל ב3 שלבים:

  1. הלכתי ל cache האחרון של גוגל והורדתי את קובץ ה index.
  2. מחקתי את כל התוכן של הbody פרט לרשימת החברים בplanet‏
  3. יצרתי סקריפט רובי קצר שיצר את קובץ הini עבור מערכת ה planet

אני מקווה כי זה יעזור לכולנו בעצם לחזור ולקרוא פוסטים מעניינים של אנשי הקהילה.

מה שונה ב רובי 2.1

לפני כחודשים שוחרר רובי 2.1, גרסת preview. הוא מכיל כל מיני שינויים, וגם ייצבו כמה תכונות חדשות אשר נכנסו ל2.0, אבל סומנו כ experimental, או במילים אחרות, לא יציבות.
כאשר אני מדבר כאן על רובי, אני מדבר על cRuby/MRI כלומר הגרסה של יוצר רובי, ולא מימושים אחרים כדוגמת jRuby או Rubinius.

להמשיך לקרוא

כשיש לך פטיש אוויר ביד …

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

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

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

שימוש בסינטרה מאפשר לי לקחת פריימוורק כדוגמת foundation או bootstrap כ css ובנוסף לשלב את ember/angular או סתם את jquery/dojo בהתאם לצורך.

עבור מסדי נתונים, אני גם מקבל גמישות, כך שאם חלילה וחס אני נאלץ לגעת ב MySQL/MariaDB (לרוב להתחבר למסד נתונים קיים) אין בעיה (לפחות ההתחברות, עם הגועל זה משהו אחר), ואם אני בר מזל ויכול לבחור מסד נתונים, אז אבחר ב PostgreSQL , ואם יש צורך אז אבחר בכלל במונגו או אולי בכלל בRedis ?

אם אני צריך, אז אוסיף למשל סוג של job queue בהתאם לצורך הספציפי והרשימה לעבודה עוד ארוכה…

כל אלו אינם קיימים בwp, היות ובתור cms הוא נועד לגישה מאוד מדוייקת שמתאימה ל cms, אבל לא אפליקצייה.

אבל כנראה גם הפעם מאסלו צדק, וכשיש  לך פטיש ביד, הכל נראה לך מסמר …

Builder – שפת DSL ליצור תוכן Markup

אחד התוספות המעניינות ביותר שיש לרובי נקרא Builder.התוסף מאפשר לנו ליצור תוכן Markup אבל באמצעות שימוש ב DSL, במקום בשימוש של Templates או inline code.

בנוסף, הוא גם נמצא בשימוש של תוספים נוספים, כדוגמת Nokogiri. שם ניתן להשתמש בו לעריכה של xml.

שימוש פשוט בBuilder יראה כך: להמשיך לקרוא

האם DSL עוזר לתכנת ?

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

כך שORM מכיל תחביר אשר מתאים להתמודדות עם מסדי נתונים (לרוב רלציונים).
מערכת לפיתוח תשתית web משתמשת בתחביר המתאים לעולם הweb, כאשר אני יוצר בדיקות, אני משתמש בשפת בדיקות וכיוב' …

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

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

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

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

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

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

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

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

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

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

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

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

סקריפטים לא קשורים

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

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

אז אני מקווה שהקוד שלי יסייע גם לכם

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.

Sequel חלק ראשון

הקדמה

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

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

על הספרייה

הספרייה מחולקת לשני חלקים:

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

התקנה

על מנת להתקין את sequel, דבר ראשון נתקין אותו, ודבר שני את הדריבר הרגיל לחיבור מסד הנתונים:

$ gem install sqlite3 sequel

התחלה

להמשיך לקרוא

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

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

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

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

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

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

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

My first ruby framework part 1

I'ved tested many static web page generators, and I found a very horrifying conclusion: They are either too big and overwhelming, or they are very small, compact, and written mostly for generating static blogs.

I'ved started to invest a day with a friend static generator, that he wrote for his blog, and even opened 11 bug reports for it, however, the generator he created is also very blog specific solution, and I want to create static web site, not a blog. The difference is in the way routing works, and the whole stat of mind when using such thing. And if I wish to make his generator do what I'm looking for, it will require from me a lot of work.

So, I'ved started yet another static web site generator (more like a framework for it imho 🙂 ), after I'ved tried not to go there in any possible way.

At the first day I'ved started planning how the skeleton of the static content will be, and started looking for libraries that can convert markup languages (markdown, reStracturedText etc…) to html, and I've chosen something that is written in Haskell, with a lot of power named Pandoc. It even has a Ruby wrapper for it, that I'm using for the project. So I jumped to the water of reinventing the wheel and creating yet another generator.

In the beginning, of writing my code, I'ved encountered several problems that needed to be solved. For example: How do I execute my executable without installing it as a Gem ? In default behaviour, if it's not installed, then Ruby does not know how to locate the files. But at the end I found an interesting solution for it:

$BASE_PATH = File.expand_path(File.dirname(__FILE__) + '/..')

I found additional problems to be solved. For example I needed to make Bundler find the Gemfile, but the executable is not part of the Gemfile path. After a lot of research and grep in the source code of Bundler, I found the answer:
It uses an environment variable named BUNDLER_GEMFILE, and it is needed to be set to the Gemfile path like so:

require 'bundler'
ENV["BUNDLE_GEMFILE"] = $BASE_PATH + '/Gemfile'
Bundler.require(:default)

Another problem that I found was in the way of parsing the cli parameters.
Ruby has it's own default library for it, but it was not built for what I was looking for.
So later on, I found a library named slop that is very nice, and works well, but not 100% of what I was looking for.
When I read the Bundler source code, I found Thor, and finally found the proper parser for me, but I will not use it in this version.

I find it very interesting how writing something like a framework takes different way of thinking then normal program.

It is very important to emphasize that at the time of writing this post, my code is still not ready, and many basic things are not implemented, and the core is constantly rewritten. But the next post will be about v0.1 that is ready 🙂

ליצור פריימוורק ברובי חלק ראשון

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

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

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

ביום הראשון להתחלתי, תכננתי איך השלד נראה, כתבתי ספסיפיקציה (עדיין לא שלמה) איך התוכן יראה לפני היצירה שלו כדף html, מצאתי המון ספריות שיודעות להמיר markup זה או אחר לhtml ובחרתי דווקא משהו שכתוב ב haskell (שימוש ראשון שלי למשהו שכתוב בשפה) אבל עם המון פוטנציאל וכוח שנקרא Pandoc . יש לו אפילו wrapper ברובי שאני משתמש לפרוייקט, וקפצתי למים העכורים של משהו שכל כך שנוא עלי – להמציא את הגלגל מחדש בפעם המליארד.

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

$BASE_PATH = File.expand_path(File.dirname(__FILE__) + '/..')

לאחר מכן, נתקלתי בעוד בעיות.
למשל היתה בעיה איך אני גורם לקובץ הריצה למצוא את Gemfile כאשר אני לא רץ בספרייה שהוא נמצא בה.
חיפוש וחפירה בהרבה מאוד קוד, גילה שיש רק דרך אחת לעשות זאת, וזה להשתמש ב environment variable של bundler בשם BUNDLE_GEMFILE :

require 'bundler'
ENV["BUNDLE_GEMFILE"] = $BASE_PATH + '/Gemfile'
Bundler.require(:default)

ועכשיו הוא יכול לעבוד כמו שצריך.

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

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

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

ניהול מאגרים מ 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 בשביל לעבוד (ככה תכננתי אותו).

תהנו.

 

ActiveRecord sucks (English)*‎

Every Ruby developer knows an ORM named ActiveRecord.
It is so common, that it has even implementation on different programming languages, such as PHP.

At one hand, it is written in a way that truly understand Ruby, but on the other hand, it is written very badly. It is written so badly, that it has a lot of SQLi bugs inside, it uses too many resources to perform tasks, and the list just goes on and on …

If that was not enough, then it is very opinionated on how things would be created, even on places that you need different level of thinking, it does not provide it on it's own, and you find yourself write stored procedures that implement them, or functions for triggers instead.

I mostly use Ruby "clean", that is not writing any web based application, and on one of the non web program that I wrote, I needed to go over million records per day (not literally a day, but day in BI), and I find that after 200 requests for records, it opens too many connections to the database, even though it has a limit for "only" 120 connections for it's connection pool, but when I decrease it for 50 connections, it claims that it does not have enough connections to use.
Even an hour of inactivity, it does not release it's connections back, even though it has a "timeout" of 30 seconds to release it.

It ignores my timeout settings, it just take more resources, but does not release them back, regardless of what I tell it to do. So it renders as resource hog, and you require to release the resources "by hand" rather then do it's job right.

I found, many complaints about it on the web, and even few bug fixes, that didn't really fixed anything as I can see in real life situation, even when going to the last stable release of it.

S I started looking at other ORM for Ruby (and there are others as well), and it looks like a problem only with AR so far.

* This is a translation that was requested by few people for the original Hebrew post.

ActiveRecord sucks

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

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

למשל יש לי מערכת לא מבוססת web בשום צורה, שעובדת עם מליוני רשומות ביום. הבעיה היא שכבר אחרי 200 גישות למסד הנתונים, למרות שה pool מוגדר רק ל120 חיבורים, אני מגלה שנפתחו עוד חיבורים שלא נסגרים. אם אני מוריד את זה ל50, אז הוא מתלונן שזה לא מספיק, אבל גם אחרי שעה של חוסר פעילות, הוא לא מסיים את החיבורים למסד הנתונים, וזה למרות שמוגדר לו להחזיר את החיבור לאחר 30 שניות ללא שימוש.

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

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

אז התחלתי לבדוק ORM אחרים ברובי (כן יש כמה וכמה), ונראה שזו בעיה ייחודית ל AR בלבד.

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

"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

בדיקות אוטומטיות אינן התשובות להכל

TDD – ‏Test Driven Development היא גישה או תפיסה האומרת כי כל שורת קוד אותה אנחנו מפתחים, אנחנו קודם יוצרים לה בדיקה שתיכשל, ואז כותבים את הקוד שיגרום לבדיקה לעבור, ובכך מונעים הרבה מאוד בעיות בפיתוח, וכן יכולים לזהות ריגרסיה כאשר זו מגיעה בקוד. כבר כתבתי על הנושא מספר פעמים כאן בבלוג.

לאחרונה היה באג בספרייה בשם ActiveRecord, אשר במצב מאוד מיוחד, היה ניתן לגרום למתודה find_by_‎ לבצע SQL Injection. הבאג ד"א היה ניתן לניצול רק במצבים מאוד מדוייקים של שימוש במתודות, ולכן אם לא היה שימוש בגישה מסויימת, לא היה ניתן לנצל אותם, אבל הבאג היה עדיין קיים, והמליצו לכולם לשדרג לתיקון הבעיה.

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

כלומר מרבית השימושים בActiveRecord מעולם לא היו גורמים לבעיות או מסכנים אתר מסויים ב SQLi. ועכשיו נשאלת השאלה: האם TDD מסוגל לעלות על מקרה קצה כל כך קיצוני ?

האם ב99.9 אחוז מהבדיקות שתעשה הבדיקה תצא תקינה, האם תדע לכתוב את הבדיקה 0.1 בה תצליח לגלות באג ?

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

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

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

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}

ה gem הראשון שלי

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

לאחרונה חזרתי אליה, ושיפצרתי אותה יותר, ואף יצרתי לה מערכת בדיקות אוטומטיות עם rspec ואפילו הוספתי תעוד של rdoc (שוקל להמיר ל yard), ובין חמישי לשישי פרסמתי את ה gem שלי לראשונה להורדה באמצעות מנהל החבילות של רובי.

אני חייב לציין שזה היה פשוט מידי לעשות את המהלך. הרשמה לrubygems, הורדת ה gemcutter מרובי gem, יצירת ה gem ואז ביצוע push עם הדוא"ל והסיסמה של rubygems וזהו 🙂

אני יודע שסטטיסטיקה חשובה לכם, אז נכון לכתיבת הפוסט היו 40 הורדות של ה gem פחות מ24 שעות לאחר הפרסום שלו, שלדעתי לפחות זה מדהים 🙂

Struct, OpenStruct ושמירת מידע ברובי

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

  1. Struct
  2. OpenStruct

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

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

התחלת עבודה עם struct:

Users = Struct.new(:username, :password, :salt, :first_name, :middle_name, :last_name, :email)
user = Users.new(:first_name => 'i')
user.username = 'ik'
user[:password] = 'secret'
user[2] = 'pepper'
user['email'] = 'spamme@example.com'
...

למעשה בהדגמה הזו, יצרתי מחלקה עם שדות בשם Users, המחלקה מכילה את שדות username, password, salt, first_name, middle_name, last_name ו email.
לאחר מכן, יצרתי אובייקט בשם user מהמחלקה Users ובעת היצירה הזנתי את השדות במספר צורות כדוגמת שימוש ישיר בשם השדה, שימוש בסימבול המייצג את השם של השדה, המיקום של השדה בסדר שיצרנו את השדות ושימוש בשם השדה, וכמובן בעת היצירה עצמה של האובייקט.

התחלת העבודה עם OpenStruct:

require 'ostruct' # mandatory to support it

user = OpenStruct.new(:username => 'joe', salt: 'sea')
user.password = '1234'

כאן יצרנו אובייקט בשם user אשר קיבל בעת היצירה שני שדות: username ו salt. כאשר OpenStruct מחייב בצורה הזו להזין להם גם ערכים.
לאחר מכן, הגדרנו שדה בשם password ונתנו לו ערך.
אלו הדרכים שאפשר להזין ולהשתמש במידע עם OpenStruct.

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

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

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

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

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

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

מלח הארץ

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

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

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

השימוש בSalt אומר כי החתימה היא בעצם הSalt פלוס הסיסמה (או הסיסמה פלוס ה Salt). יש עוד הרבה דרכים לעשות את זה, כדוגמת הוספת ביטים/בתים לסוג הhash וכו' כאשר משתמשים בSalt, אבל העיקרון בסופו של דבר אומר, כי החתימה של הסיסמה זה Salt וסיסמה בייחד, ולא רק הסיסמה לבד.

כאשר "חותמים", כיום מומלץ בחום לא לעבוד יותר עם md5, היות ויש לו התנגשויות. כלומר ישנה יותר מסדרת בתים אחת שיכולה להיות עם אותה חתימה. אז אני משתמש ב sha256 במקום. הנה קוד רובי המדגים כיצד יש לבצע זאת: להמשיך לקרוא

שירים מתקדמים עם סינטרה חלק ראשון

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

כאשר התחלתי לחקור מה אני צריך לנושא, התחלתי לעשות בדיקות של הרבה מאוד סביבות עבודה, בניהם django, rails, ramaze, dancer, padrino, tornado ועוד מספר מערכות מבוססות Node.js.

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

אז למרות שמאוד פחדתי, הלכתי ובדקתי את Sinatra (שוב). חשוב לי להדגיש כי זהו framework מדהים אשר אני מאוד אוהב אותו, אבל הוא נראה לי מנימליסטי מידי לצרכים שלי.

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

Redis Session for Ruby

I'ved created a standalone session system based on Redis database.
This session system was not build only for web in mind, but also for non web based systems.
I for example, use it with Asterisk based programs that I'm writing to clients.

The session lib supports at the moment only few features:

  1. Connect to Unix sockets
  2. Connect to TCP sockets, and changing the port number if needed
  3. Expiring all keys
  4. Expiring specific keys
  5. Changing expiry of a specific key
  6. Checking expiry of a specific key
  7. Saving and restoring Ruby based structures such as hashes, arrays, boolean etc…
  8. Removing keys
  9. Prefix that will be automatically added to each key name (in saving and restoring)

The usage of the system is very simple:

require 'rubygems'
require 'redis_session'
session = Session::SessionClient.new(:prefix => 'add_test') # init the session
session.save('name', { 'name' => 'session'}) # Saving a content. Pure ruby content
restored = session.restore('name') # return to me a pure ruby content
puts restored.inspect
session.remove('name') # delete the key
view raw sessoin.rb hosted with ❤ by GitHub

The current version (0.1.3), does not contain yet any documentation. However it will be added in next version.
The library itself is under the MIT license, and can be downloaded here.

You can create your own gem file. I hope that by the next version, it will be created automatically.

* You must have active Javascript support for wordpress.com and github to view the source code.

השוואת טיפוס נתונים ב Ruby עם case

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

עם case היה מאוד טבעי לעשות משהו כזה:

a = {'one' => 1 }
puts "a.class = #{a.class}"
case a.class
when Hash
puts 'Hash'
else
puts 'not Hash'
end
view raw case1.rb hosted with ❤ by GitHub

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

a = {'one' => 1 }
puts "a.class = #{a.class}"
case a
when Hash
puts 'Hash'
else
puts 'not Hash'
end
view raw case2.rb hosted with ❤ by GitHub

 

* צריך תמיכה ב Javascript גם ב wordpress וגם ל gist על מנת לצפות בקוד.

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

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

לרובי יש שלוש צורות שאני מכיר, על מנת להריץ מתודות בצורה דינאמית. הדרך הראשונה היא שימוש ב 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 🙂

חבילות ל CentOS

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

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

כרגע יש 2 חבילות:

בעתיד אוסיף עוד חבילות במידת הצורך, ואולי אמזג את זה לעץ אחד.

כרגע אתם מוזמנים כמובן לעשות לי Fork בGitHub 🙂