2015-03-26

AWS: להכיר את S3 מקרוב

בהמשך לפוסט "הצצה ראשונית ל AWS" התחלתי לכתוב פוסט על ה Big Data Stack של AWS, אך מהר מאוד נתקעתי: הבנתי שחסר רקע ב Hadoop, ורקע קצת יותר מקיף על שירותי הבסיס של AWS.

שירותי הבסיס של AWS? מלבד EC2 (שהוא מרכזי מאוד, אבל לא כ"כ מפתיע), שירות חשוב מאוד הוא S3, ולו החלטתי להקדיש את הפוסט הבא.

S3 (קיצור של Simple Storage Service, להזכיר) הוא שירות האכסון הבסיסי של אמזון, והוא שימושי מאוד בתסריטים רבים. הממשק שלו דומה למערכת קבצים מבוזרת (אם כי הוא קצת יותר מורכב).

את הקבצים מארגנים ב Buckets - שהם סוג של Root Folders, עליו ניתן להגדיר הרשאות ועוד מספר תכונות.
לכל קובץ ניתן לגשת ישירות בעזרת HTTP, בצורה:

s3://bucket/folder/filename

כאשר קבצים יכולים להיות בגודל של עד 5TB.

למרות הממשק הדומה למערכת קבצים, כדאי לזכור שבגישה ל S3 יש latency של רשת + ה latency הפנימי של S3 (שיכול להיות עוד 100-200ms בממוצע). אל תנסו לרוץ בלולאה על קבצים ב S3 בזה אחר זה, כמו שאולי אתם עשויים לעשות עם מערכת קבצים מקומית. להזכיר: זמן גישה חציוני למערכת קבצים מקומית עשויה להיות משהו באזור ה 10ms..., ואין לה את המורכבות הנוספת של הרשת.



Bucket, הסמל של S3


כאשר יוצרים Bucket, ניתן לבחור להגדיר אותו באחת מ2 רמות אמינות קיימות:
  • רמת רגילה, המספקת durability של 99.999999999% (11 תשיעיות, וואהו!)
  • Reduced Durability (נקראת RRS, קיצור של Reduced Redundancy Storage) - המספקת durability של 99.99% בלבד. כלומר: סיכון של 0.01% אחוז, כל שנה, לאבד את המידע. המחיר שלו נמוך ב 15-20% מהתצורה הסטנדרטית. תצורה זו מתאימה למידע לא קריטי / שניתן לשחזר.
בכל מקרה ה availability של s3 הוא 99.99%, כלומר: לעתים לא תוכלו לגשת לקובץ (availability), למרות שהוא עדיין קיים (durability). תוכלו לגשת אליו זמן קצר מאוחר יותר.



מה אני יכול לעשות עם S3?


פחות או יותר כל מה שאפשר לעשות עם מערכת קבצים מבוזרת, אמינה מאוד, מהירה יחסית, ובעלת קיבולת בלתי מוגבלת. השימושים הנפוצים הם:
  • הנגשה של תוכן סטטי (קבצי HTML/CSS/תמונות וכו', לעתים קבצים JSON או XML עם נתונים) על גבי HTTP. ל S3 ניתן לגשרת ע"י REST ו/או SOAP.
  • שיתוף של נתונים, בצורה מבוזרת, בין כמה שרתים. לעתים כאשר קצב הקריאה הוא גבוה מאוד.
  • שמירה של כמות גדולה של נתונים סטטיסטיים לא מעובדים / מעובדים קלות - עבור עיבוד עתידי (מה שנקרא "Data Lake")


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

ניתן להשתמש ב awscli בכדי לבצע פעולות על s3 מתוך command line:
  • ליצור bucket (פקודת mb), להציג את רשימת הקבצים שנמצאים ב s3 (פקודת ls) או להעתיק קבצים בין s3 למחשב המקומי (פקודת cp), וכו'...
  • פקודת sync - לסנכרן תיקיה מקומית מול bucket של s3. הפקודה תגרום רק לקבצים חדשים, בעלי גודל שונה, או תאריך עדכון חדש יותר מאשר ב s3 - להיות מועלים ל s3. הפרמטר delete-- יגרום לפקודה לנקות מ s3 קבצים שנמחקו מהמחשב המקומי.
דרך שלישית ומקובלת היא להשתמש ב Programmatic APIs.

אם אתם עובדים על "חלונות", יש כלי UI נחמד בשם S3 Browser, המאפשר לראות את ה Bucket ולבצע עליו פעולות בצורה נוחה (משהו כמו כלי FTP נוח).



ה UI של S3


על כל bucket יש מספר תכונות:
  • הרשאות: מי רשאי לגשת לקבצים ב bucket? ניתן לאפשר גישה ציבורית (ע"י HTTP url). ניתן גם לקבוע הרשאות ברמת הקובץ הבודד.
  • ניהול גרסאות: כל שינוי לקובץ ב bucket ינוהל בגרסה (כולל מחיקה). ריבוי העותקים יגביר את העלויות, וניתן לקבוע מדיניות ("lifecycle rules") מתי ניתן למחוק עותקים ישנים או להעביר אותם ל AWS Glacier (אכסון זול בהרבה, במחיר גישה אטית לקבצים - יכול לקחת גם כמה שעות בכדי לעשות checkout לקובץ).
  • האזנה לאירועים: ניתן להאזין להוספה / מחיקה / עדכון קבצים ב bucket, ולשלוח הודעה לאחד משלושה שירותים של AWS:
    • Simple Notification Service (בקיצור SNS) - שירות ה publish/subscribe של אמזון. מאפשר לכמה לקוחות להירשם ל topic, ושולח את ההודעות ב push (ל HTTP endpoint, דוא"ל, או SMS).
    • Simple Queue Service (בקיצור SQS) - שירות queues. על הלקוח לשלוף ביוזמתו את ההודעה מה queue, ומרגע זה - ההודעה כבר לא קיימת יותר. לעתים קרובות מחברים את SNS שישלח הודעות למספר SQS queues - אחד לכל נמען.
    • AWS Lambda - בכדי להריץ פונקציה על בסיס השירות.
  • אירוח אתרים סטטיים (Web Hosting) - על בסיס קבצי html/css/javascript שמועלים ל s3, בשילוב עם Route 53 (שירות ה DNS של אמזון).
  • הצפנה (server side encryption): אמזון יכולה להצפין עבורנו את הנתונים הנשמרים ב s3, ע"פ מפתחות שאנו מספקים לה. אם מישהו פרץ לתשתיות של אמזון (או קיבל צו חיפוש פדרלי בארה"ב - למשל), הוא מוצא קבצים מוצפנים שאין בהם הרבה שימוש.
  • אינטגרציה ל Cloudfront - שירות ה CDN של אמזון, המאפשר להנגיש קבצים המאוחסנים ב S3 בעלויות נמוכות יותר, ו latency קצר יותר (על חשבון: עד כמה מעודכנים הקבצים שניגשים אליהם).
  • Multipart upload - המאפשר לחתוך קבצים גדולים לכמה parts ולטעון אותם במקבלים על גבי כמה HTTP connections.
  • Logging - שמירת לוג של הפעולות שנעשו על ה Bucket.

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




היבטים של Landscape


S3 הוא שירות ברמת ה Region, ובאופן אוטומטי תהיה רפליקציה של הנתונים בין ה Availability Zones השונים. הרפליקציה מתבצעת תוך כדי כתיבה, כך שאם קיבלתם OK על הפעולה - המידע שם ומסונכרן (בניגוד ל offline replication המתבצע מאוחר יותר).

שם של Bucket צריך להיות ייחודי ברמת כל ה Regions של AWS (כלומר: Globally unique ב Account).
שם האובייקט משפיע על האופן שבו S3 תעשה partition לנתונים, ולכן אם אתם זקוקים ל tps גבוה - כדאי לדאוג לשמות בעלות שונות גבוהה, ושאינם תלויים בדפוסים שלא מייצגים את דפוסי הגישה (למשל: לא להתחיל שם של אובייקט ב timestamp - כי יהיו קבצים רבים המתחילים באופן דומה). הנה פוסט בנושא, אם הנושא רלוונטי עבורכם.




Policies


על Bucket ניתן לקבוע policies, המוגדרים מכמה אלמנטים:
  • Resources - על אילו משאבים (קבצים) ב bucket אנו רוצים להחיל את ה policy.
  • Actions - אלו פעולות על הקבצים אנו רוצים להשפיע (upload, list, delete, וכו')
  • Conditions - באלו תנאים יחול ה Policy: שעות פעילות מסוימות, regions של AWS, מצב של הקובץ, וכו'. בעצם ב conditions נמצא הכוח האמיתי של ה Policy.
  • Effect - משמעות: allow/deny. אם יש סתירה בין policy של deny ל policy של allow - ה deny policy הוא זה שיכריע.
  • Principal - החשבון ב AWS או IAM user עליו חל ה policy.
Policy לדוגמה יכול להיות הכנסת ססמה לפני מחיקה של קבצים מה Bucket, בכדי לצמצם את הסיכון שמישהו מוחק נתונים קריטיים, בטעות. ה Durability הגבוה של S3 הופך את הגורם האנושי לחלק המסוכן בשמירת המידע.

את ה policy מגדירים כקובץ json ע"פ מבנה מסוים, ועושים לו copy-paste לתוך ה UI (ב properties של bucket / הרשאות / policy). אמזון (בצדק) לא יצרו את ה UI המורכב שהיה נדרש בכדי לאפשר להגדיר policies בתוך ה UI של ה bucket. ניתן להשתמש ב AWS Policy Generator בכדי לייצר Policies (אך לא לערוך או למחוק) ואז להדביק את התוצאה ב UI של ה bucket properties.


Policy לדוגמה. מקור: אמזון



סיכום


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

שיהיה בהצלחה!




---

לינקים רלונטיים

FAQ של אמזון על S3
המחירון של S3
Data encryption on S3



2015-03-25

רשמים מכנס רברסים 2015 [פוסט אורח]

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

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

אז מה זה רברסים? למי שלא מכיר -  פודקאסט ישראלי (http://www.reversim.com/) בנושאי תוכנה שמשדרים אורי להב ורן תבורי מאז 2008. הפודקאסט הזה הוא אחד הגורמים שהשפיעו המון על הסטרטאפים הישראלים שקמו בשנים האחרונות, משום שהוא נתן את הטון לשיתוף ידע ואוירה קהילתית.

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

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




קצת על הנוכחות הנשית בכנס

היה לנו מספר עגום ביותר של נשים נוכחות (לדעתי לא יותר מ 20, כשמספר הרשומים בכל יום עבר את ה 300).

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

את הביתן של Rails Girls ארגנו אילונה ונטלי - זהו הסניף הישראלי של Rails Girls שמארגן אירועי קידוד והאקתונים לנשים ולבנות.

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

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

אגב, את הרעיון קיבלתי מכנס על Mobile שהייתי בו בחו"ל לפני כמה שנים. קיבלתי אז מייל כמה ימים לפני הכנס שהזמין אותי ל women's networking lunch. גם שם היינו כ-20 נשים, וישבנו בשני שולחנות, פשוט לאכול יחד ולשוחח.

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








קצת על הנושאים בכנס


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

ההרצאות ההמעניינות ביותר בעיני היו Architecture Overview על המערכת הקיימת של חברה מסוימת, עם כמה דגשים:
  • איך התפתחה הארכיטקטורה עם הזמן כדי לענות לבעיות (בעיקר scalability אבל לא רק) 
  • עקרונות ותבניות של הארכיטקטורה (והמנצח השנה, טמ-טמ-טמ: Micro-Services !!!) 
  • רכיבי open source מרכזיים בפתרון. עבורי, זזו היתה אחת ההזדמנויות לעשות קורס מזורז בטכנולוגיות החמות והמוכחות ביותר, ברמה של - "שם", "מספר אישי", ו"תפקידך בכוח".
מוטיב נוסף שחזר בכמה הרצאות היה - שפות מבוססות JVM
Closure, שפה פונקציונלית המזכירה את Scheme/LISP, הוזכרה כשפה שנבחרה למימוש אחת המערכות. הרצאה אחת הוקדשה לסקאלה (Scala) ואחת לגרובי (Groovy), כולל טיפים לאיך להכניס את השפות הללו לפרויקט java קיים. לקחתי מההרצאות האלה כמה רעיונות פרקטיים לפרויקט שלנו.

הרצאות ה ignite היו מדליקות - זה רצף של הרצאות קצרות של 5 דקות כל אחת, בעירוב נפלא של נושאים טכניים ו soft skills. למדתי דברים חדשים מההרצאה על HTTP 2.0

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



כל השקפים מצטברים לאט בלינק הבא: https://hackpad.com/RS15-Presentations-htx5kKsSeCv
הקלטות של ההרצאות יפורסמות בעתיד.


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






Scaling with micro services - Wix architecture:
  • Clusters divided to end user view (public) / editing / media serving
  • Micro services architecture…but other services can read directly from other services DB
  • No DB transactions at all (only applicative transactions / eventual consistency)
  • Save logic in the editor: Save each page in a separate REST call, where page ID is a hash on the content. Then save the site object , with a list of all pages (new IDs) to complete the “transaction”
  • Media is stored in several places: CDN + Google cloud + amazon, and fallback logic to all locations. If everything fails – media is loaded from Wix servers.
  • JS code renders a json configuration object – avoid server side HTML rendering for maximum scale. 4 servers serve all of Wix traffic.
  • SEO content requires server side HTML rendering and for that they are a separate cluster of 12 servers.

Some limitations\challenges in the microservices architecture
  • Debugging is not easy
  • They implemented no dependency management between the services
  • API versioning, as APIs (within their version) do not change. Need forward + backward compatibility

















Reactive by Example

Slides are here

The reactive manifesto describes a common pattern for distributed systems:

A reactive system is:
  • Responsive - requires quick responses. predicted response time + QoS (Quality of Service).
  • Resilient - remains responsive even in case of a system failure. Failure is to be expected. Almost a "1st class citizen" in the system's design.
  • Elastic - can grow up/down in scale, according to traffic's requirements.
  • Message-Driven - asynchronous, loosely coupled, messages
A “metrics collecting system” was described as an example of a reactive pattern system.
Open source components mentioned, that were used:














MicroServices and Event-Driven architecture with Clojure and Kafka

The business is to analyze marketing campaigns on mobile applications.
This was another example of a system architecture rapidly scaling rapidly (during 1.5 years) from:
10+services, 250M events\day, 5 servers+3DBs

into:
100+ services, to 2.5B events a day, 200 servers+20DBs

Architecture highlights:
  • All raw data is stored in S3
  • Services communicate with events, IO is non-blocking
  • Event stream is using Kafka (high scale messaging system)
  • key attribute is that the event can be consumed by multiple consumers and is not erased from the queue.
  • A “conveyor belt” analogy
  • Not fast enough for real-time response (OK for this use case)
Other elements in their architecture:
  • Clojure as programming language (see below). Motivation: JVM-based, enforces functional paradigm.
  • Consul for service discovery
  • Docker for service deployment
  • Statsd for JVM metrics
  • Testing is done directly on production systems (no test systems!!!)












JVM-based languages

In 3 different lectures they addressed the next generation of JVM-based languages, after Java.

The common of all of them:
  • All Running on JVM and compile to bytecode, 
  • Leveraging one of the best runtime engines that exist today
  • Compatible with other development done in Java

Clojure
  • Enforce functional paradigm.
  • Suitable for sequence-based processing, very suitable for event processing.

Scala
  • Everything is an expression (not a statement)
  • Remove a lot of coding overhead (“boilerplate code”)
  • Strong collection framework
  • Functions are first-class objects (Like JS)
  • Option – a cool way to eliminate the need to handle null in your code (Option is an array of size 1 or 0, enforcing you to always take into consideration the fact that the value may not exist there)\
  • Traits – partially implemented interfaces, a good way to model effective multiple inheritance
  • Lazy evaluation built into the language, thus preventing the need to code if (debugLevel>INFO)
  • Domain specific languages. E.g. a powerful tests
  • Java 8 took many great ideas from Scala but still there’s an inherent gap 

Groovy
  • Java code is also groovy code so you can have a gradual adoption
  • You can have a java interface and groovy implementation (Need to implement Groovy Loader)
  • JSonSlurper – parse json objects, and then access them in an JS-like way (addressing the names of the properties)
  • RESTClient/HttpBuilder for easier work with HTTP/REST
  • Now to become part of Apache foundations


Recommendations how to add a new JVM-based language to an existing project
  • In the tests
  • For new projects
  • In a specific layer
  • For an isolated component



Other information fragments \ Interesting technologies


Storm in under a second
Storm in a framework for event streaming and processing, implemented in Java.
Its deployment can be done on a single machine
The lecture was about how to achieve a controlled response time with an events framework, which theoretically does not guarantee response time


HTTP 2.0
How it overcomes many limitations in how browsers work today
Good source is: http://daniel.haxx.se/http2/http2-v1.11.pdf


Gamification of code reviews
An open source plugin for git hub providing a count of how many people commented on code differences, including leaderboard
https://github.com/tzachz/github-comment-counter
Advantage – publish the people who do the most code reviews. Gamification!!!



סיכום

ליאור: תודה רבה לרחלי אבנר על הפוסט! כן ירבו.




2015-03-09

מושגי יסוד ב Geospatial Processing

GIS הוא קיצור משעמם של Geographical Information Systems - "מערכות מידע גיאוגרפיות", אבל חלק ממה שנעשה עם טכנולוגיות אלו הוא בכלל מגניב למדי: Waze, המפות של גוגל, GetTaxi, מערכות שליטה צבאיות, ועוד משתמשות בטכניקות של Geospatial Processing (מקור: spatial = מרחבי, נשמע כמו "ספיישל", בדומה ל Special "ספשל") בכדי להגיע לתוצאות.

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


כלי Geospatial קיימים מתחלקים לכמה קבוצות:
  • מערכות GIS קלאסיות, המפשרות לערוך מפות, לצפות בהן, ולבצע חיתוכים ושאילתות. המערכות הנפוצות בתחום זה הן ArcGIS (בתשלום) ו QGIS (תוכנה חופשית).
  • מנועי חישוב Geospatial, היודעים לבצע חישובים על נתונים גיאוגרפיים. חישובים לדוגמה הם:
    • מציאת מסלול קצר ביותר בין 2 נקודות (proximity)
    • מציאת k אובייקטים מסוג מסוים הקרובים ביותר לנקודה נתונה (k-nearest)
    • מציאת הגוף הקמור המינימלי המכיל קבוצת נקודות גיאוגרפיות (convex hull)
    • פישוט התיאור של צורה גיאומטרית לייצוג פשוט יותר, עם מינימום שינוי (simplification)
    • פעולות חיתוך / איחוד / מציאת שונות בין פוליגונים במרחב (intersection, disjoint, ..., וכו')
    • ועוד
      המנועים הנפוצים בתחום (קוד פתוח) הם JTS (בג'אווה), GEOS (פורט של JTS ל ++C), ו GDAL (כתובה ב ++C, מתאימה למידע raster, שאינו וקטורי). כמובן שיש גם כמה מנועים proprietary.
  • בסיסי נתונים Geospatial, אלו בסיסי נתונים "רגילים" היודעים לאחסן בעמודות/מסמך (תלוי בסוג בסיס הנתונים) מידע גיאוגרפי, ואז לבצע עליו חישובים. הרבה פעמים מדובר על בסיסי נתונים מוכרים עם extension ליכולות ה geospatial.
    בכדי לבצע חישובים יעילים של מרחקים, יש לבנות על הנתונים הגאוגרפים אינדקסים במבני-נתונים ייעודיים כגון k-d tree, או R-tree.
    k-d tree, למשל, הוא עץ בינארי, כאשר כל רמה שלו מתארת חיתוך של ציר אחר, למשל: רמה 1 לציר x, רמה 2 לציר y, ואז רמה 3 שוב לציר x וכו'. חיפוש במרחב עליו יהיה יעיל הרבה יותר מ B-tree - מבנה הנתונים ה"גנרי" לאינדקסים בבסיסי נתונים.
    • בעולם הקוד הפתוח: PostgreSQL ו MongoDB הם המובילים, ל MySQL יש יכולות Goespatial בסיסיות בלבד.
    • בעולם ה Commercial ניתן למצוא את אורקל (כמובן), MS-SQL (מאז גרסה 2005) ו DB2, שגם להם יש יכולות Geospatial.
    • יש גם בסיסי נתונים ייעודיים למידע גיאוגרפי - והם פחות מוכרים.
למשל: PostgreSQL מנצל את היכולת המובנה שלו להגדרת טיפוסים חדשים, בכדי להגדיר מבנים גיאוגרפיים (למשל: פוליגון) ובעזרת extension בשם PostGIS (המכיל את GEOS) - הוא מאפשר לבצע שאילתות SQL שגם מבצעות חישובים.
MongoDB, באופן דומה, מנצל את היכולת שלו לאחסן מסמכים (GeoJSON הוא פורמט פופולרי לייצוג מידע גיאוגרפי), ובשילוב מנוע חישוב הוא מאפשר לבצע שאילתות על המידע הגיאוגרפי.



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






ייצוג מידע גיאוגרפי


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


מערכת קורדינטות

מערכת קורדינטות מדויקת המתארת מיקום על פני כדור הארץ, היא לדוגמה מערכת פולארית (נקראת בתחום: geodetic) כמו lat/long:

Latitude (בקיצור lat)
קו רוחב - מיקום הנקודה מעל או מתחת לקו המשווה.

Longitude (בקיצור long)
קו אורך - מיקום נקודה היא ממזרח או ממערב לקו גריניץ' - קו דמיוני שעובר בעיר לונדון.

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

בניגוד למערכת הקורדינטות שמקובלת בגרפיקה של מחשב, בהן נקודת ה 0,0 היא בפינה השמאלית עליונה של המסך וערכי הקורדינטות הם תמיד חיוביים, מרכז מערכת הצירים של מערכת ה lat-long נמצאת במרכז "המסך" (איפשהו במפרץ גאנה שבאפריקה) ויש לקורדינטות ערכים חיוביים ושליליים. טווח הערכים של lat היא 90- עד 90 מעלות, בעוד טווח הערכים של long הוא 180- עד 180 מעלות , כפי שניתן לראות בתרשים למטה.
את הערכים מייצגים בצורה עשרונית, למשל:

-71.060316 48.432044

lat/long (לפעמים נקראת lat/lon, ולפעמים הופכים את סדר הצירים: long/lat) היא המערכת המקובלת בתחום.
אתם יכולים לשחק דקה עם האתר latlong.net, בכדי לקבל מושג על הערכים שמתקבלים.

מערכת דומה מאוד היא המערכת בה משתמשים ב GPS, בה מעבר למעלות, מחלקים את השבר העשרוני ל "דקות" ו"שניות" (במקום נקודה עשרונית), ובמקום ערך שלילי/חיובי מציינים כיוון גיאוגרפי (צפון/דרום), מה שנראה כך:

40° 26′ 46″ N 79° 58′ 56″ W







חישוב מרחקים
כיצד מחשבים מרחק בין 2 נקודות?

חישוב המרחב הפשוט ביותר היא חישוב טריגונומטרי של משולש: צלע ראשונה היא ממרכז כדוה"א לנקודה א', צלע שנייה
היא מממרכז כדוה"א לנקודה ב' - וצלע שלישית היא המרחק.
חישוב נאיבי זה של מרחק (נקרא גם linear distance) הוא טוב למרחקים קצרים, אך איננו מדויק. המסלול בפועל בין 2 הנקודות הוא מעגלי - על פני כדוה"א. במצבים מסוימים (תלוי בנקודה לאורך כדור הארץ), מרחק אמיתי של 30 ק"מ יחושב כ 29.7 ק"מ בשיטה הטריגונומטרית. טווח זה של טעות מתחיל להיחשב כבעייתי עבור שימושים רבים.

ניתן כמובן לחשב angular distance, על בסיס כדור - מה שמספק קירוב לא-רע.
כאשר המרחקים גדולים (מאות קילומטרים) חשוב להשתמש להשתמש בנוסחאות המתארות צורה יותר מדויקת של כדור הארץ.מכיוון שכדור הארץ הוא כדורי-אליפטי (ellipsoid, או לדקדקנים: oblate spheroid), הערך של קו רוחב במטרים, למשל, איננו קבוע ומשתנה ממרכז לקצוות כדור הארץ. החישוב המדויק של מרחקים (או למשל: שטח) בהינתן תיאור ה ellipsoid הוא כבר דיי מורכב, וגוזל זמן חישוב גדול יותר.

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

אופן פישוט אחד לדוגמה הוא להתייחס  לכך שבממוצע, 111.32 ק"מ הם מעלה אחת של lat/long - כאשר יש הבדל (= טעות מירבית) של כ 1% בין קו המשווה לקוטב (בגלל הצורה האובלית של כדוה"א).

מקובל להשתמש בערך 0.000008998719243599958 בכדי לתאר בממוצע מטר, במעלות של lat/long.

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

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


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

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



בעיות מידול של מפות כדוה"א, המשפיעות על מערכות Geospatial


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

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




מיפוי מפורסם למדי, למשל, נקרא Mercator Projection, שווריאציה שלו משתמשים המוצר Google Maps.
זהו מיפוי ממשפחת ה Cylindrical Projection, אשר נוטה לעוות יותר את הקטבים על חשבון עיוות פחות של קו המשווה.

את העיוות של ה projection ניתן להציג בעזרת התרשים הבא:

תוצאה של Mercator Projection, אל מול הגודל האמיתי של אוסטרליה וגרינלנד. מקור: וויקיפדיה

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

ה Mercator Projection משמש גם כבסיס למערכת קורדינטות בשם UTM (קיצור של Universal Transverse Mercator). מערכת ה UTM מתעלמת מהקטבים (בהם העיוות של Mercator Projection הוא גדול במיוחד), ומחלקת את השטח שנותר ל 60 רצועות הנקראות UTM zones, ביניהן יש גם חפיפה קטנה:

UTM Zones של ארה"ב, על גבי projection קוני (לכן העיוות). מקור: וויקיפדיה.
בתוך ה UTM Zone הקורדינטות מחושבות במטרים, יחסית ל zone. מרכז ה UTM zone הוא 500,000 על 0. הסבר:
500,000 מטר למזרח (כך שניתן יהיה לתאר נקודה מערבית בערך חיובי), ובעוד הערך במטרים לצפון / דרום הוא גם חיובי, אך יש לציין N או S - האם הנקודה היא דרומית או צפונית לקו המשווה.

למשל מגדל CN בקנדה נמצא ב Zone 17, ובתוכו 630 ק"מ מזרחה ו 4833 ק"מ צפונה: 130 ק"מ מזרחית ממרכז ה zone, ו 4800 ק"מ צפונית מקו המשווה. בתחביר מקוצר מתארים זאת כ:

17N 630084 4833438
(להזכיר: הקורדינטות הן במטרים)

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





מבני נתונים לתיאור מידע גיאוגרפי


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

Point
נקודה גיאוגרפית, שלרוב מיוצגת בעזרת הצמד lat ו long.

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

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


נתקלתי בפורום כלשהו בסיפור הבא:
מישהו ניסה לשמור מעגלים מעל PostGIS, אבל לא מצא פקודה מתאימה. לשמחתו הוא מצא ספריית עזר שעושה זאת. הספרייה סיפקה פקודת create_circle ליצירת עיגול, אבל מתחת לקלעים יצרה תמיד מתומן (מצולע עם 8 צלעות) בשטח זהה לזה של המעגל המבוקש - ושמרה אותו כפוליגון. הפונקציה get_circle החזירה פרטים של עיגול אותו חישבו מהפוליגון שנשמר - ששימש את הבחור לתצוגה למשתמש.
הבעיה הגיעה בזמן ריצה: במקרה מסוים הבחור שמר מעגלים ברדיוס של 600 ק"מ ולא הבין למה פעמים רבות, נקודות שאמורות להיות במעגל - לא נמצאות בו (!!). #פשעי-אבסטרקציה.

Geometry
טיפוס-העל המתאר כל סוגי הצורות הוקטוריות: נקודה , מחרוזת-קווים, פוליגון, וכו'. כלומר: נקודה היא גאומטריה, כפי שפוליגון הוא גיאומטריה.
פעמים רבות ייעשה שימוש בגיאומטריה במערכות צירים דו-מימדיות המתארות מרחב שטוח (למשל UTM), אך ניתן להשתמש בהן גם עבור מרחבים כדוריים (למשל lat/long).

מערכות שונות מוסיפות מבני נתונים משלהן: למשל PostGIS תומך גם ב CircularString (מחרוזת-קשתות) ו Polygon with holes (לדוגמה: לתיאור אגם עם אי). צורות נוספות הן ל יש גם מידע Raster (כמו תמונות לווין) שהן לא גיאומטריה אלא ממש bitmap - לא אתייחס אליהן בפוסט זה.


Geography
(התחייסות ל PostGIS או MS-SQL) - גאומטריה, המנוהלת בהכרח במערכת קורדינטות lat/long - אך ערכי החישוב בה (למשל: מרחק) הם במטרים - ולא במעלות, כפי שהיינו מקבלים מגיאומטריה המתוארת ב lat/long.



סמנטיקה

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

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

Layer או Feature Class
קבוצות של features מאותו הסוג, למשל: אוסף כל הכבישים המהירים, כל הכבישים הרגילים, כל תחנות הרכבת או כל הקניונים. הרבה פעמים מאפשרים ב UI למשתמש להציג/להסתיר Layers (ומכאן המטאפורה / מקור השם).



Spatial Reference Systems


בעולם גלובאלי, רב-תרבותי כמו שלנו - אי אפשר מבלי לסבך קצת את הדברים עם תקנים שונים.
בכדי להעביר נתונים גיאוגרפיים בין מערכות, יש להתאים שורה של תנאים:
  • ה ellipsoid (צורת כדור אליטפטי) שנבחר לייצג את כדור הארץ. אני למשל אוהב במיוחד את grs1980, אבל לא כולם כמוני...
  • datum - (שיטת החישוב של) נקודת / נקודות הציון על פני כדור הארץ לפיה "מעמדים" את ה ellipsoid. הנקודות יכולות להיות במקומות שונים ובגבהים שונים. המטרה שלה להגדיר את ה geoid - המשטח הדמיוני של ה ellipsoid על פני כדור הארץ האמיתי. השיטה המקובלת ביותר היא WGS84, אך גם NAD27 ו NAD83 הן נפוצות. יש בסה"כ מאות שיטות קיימות.
  • מערכת קורדינטות - כמו lat,long או זו של ה GPS. 
  • מבנה הקובץ. מבנים מקובלים הם WTK (מבנה טקסטואלי מסוים), WTB (גרסה בינרית), GML (מבוסס XML), או GeoJSON.

שימוש ב datum שונה ממה שהתכוונו אליו - יכול לגרום לטעויות של מאות מטרים באיכון נקודות על המפה. שיטות קורדינטות שונות או מבנה קובץ שונים... אתם מבינים.

סה"כ יש שיטות המרה סטנדרטיות בין ה datums וה ellipsoids השונים, כך שאם ידוע פורמט המקור - מנוע החישוב שבו אתם משתמשים כנראה לא יתקשה לבצע את ההמרה. ב PostGIS, למשל, משתמשים בקוד בשם SRID (ערך לדוגמה: 4326) בכדי לבטא את ה reference system שבשימוש. משתמשים בו בכדי להגדיר את ה reference system של עמודה חדשה עם מידע גיאוגרפי שיוצרים, או בהכנסה של נתונים (שאז אם ה reference system הוא שונה - תתבצע המרה).


סיכום


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


שיהיה בהצלחה!



-----

לינקים רלוונטיים

USA National Map Viewer - מערכת GIS שמנוהלת ע"י ממשלת ארה"ב. ניתן להשתמש במערכת אונליין, או להוריד נתונים (באיכות טובה) על ארה"ב.



2015-03-06

ריילס: routing

נדבך חשוב בריילס הוא ה routing, המיפוי איזה Action (=מתודה) של איזה Controller תופעל ל URL נתון.
הרכיב שעושה את ה routing נקרא ActionDispatcher.

ה routing מתבצע באפליקציה בקובץ בשם config/routes.rb בעזרת סט פקודות מיוחדות (בעצם: DSL) שמגדיר את ה routes. סט הפקודות הזמין הוא עשיר ומגוון למדי, ולרוע המזל - אינו מתועד בצורה נוחה ללמידה. לא כ"כ באנגלית - ובוודאי שלא בעברית.


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

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

כפי שנראה ה routing של ריילס מבוסס עמוקות על עקרונות ה REST - ניתן לקרוא בקצרה על עקרונות ה REST בפוסט הזה על REST או בפוסט על HTTP.
לפני ריילס 3, הגדרת ה routes הייתה מסורבלת למדי. אני מתעלם מתחביר זה לחלוטין, ומתמקד במה שזמין בריילס 3 ו 4 (או ליתר דיוק: מתייחס למצב בריילס 4.2).





האנטומיה של route


הנה דוגמה ל route טיפוסי:

  1. HTTP verb / method
  2. pattern של URL יחסי, המכיל בתוכו:
  3. segment key (אחד או יותר) - המוגדר כ "symbol" ב path.
    segment key ממפה ארגומנטים שמועברים ב URL (או כ Query String[א]).
  4. יעד המיפוי, בפורמט: "controller#action". שם ה controller מופיע ללא המילה Controller ובאותיות קטנות (בכדי לקצר בכתיבה). Action הוא שם המתודה ב controller שמטפלת באירוע.
  5. רשימת אופציות ל routing. במקרה שלנו יש אופציה אחת בשם "as:" עם ערך של "purchase" (הסבר על אופציה זו - בהמשך).

routes יכולים להיות מוגדרים בצורות שונות, ואף מורכבות יותר - על מבנים אלו נדבר בהמשך.

בריילס 3, היה מקובל להגדיר routes פשוטים בעזרת פקודת match, למשל:

match 'products/:id' => 'products#show', via: :get

via הוא פרמטר שמגביל את ההתאמה ל HTTP verb/method מסוים (אפשר גם לשלוח רשימה - במערך), והוא היה אופציונלי עד גרסה 4 של ריילס. בגרסה 4 זו הפכה לחובה (RuntimeError ייזרק אם לא הוגדרה http method, אפשר להשתמש גם ב any:, אם כי לא מומלץ).

הדרישה להגבלת ה http method נולדה משיקולי אבטחה ואמינות של המוצר. מומלץ תמיד להגדיר HTTP verb יחיד ל route, ולצורך כך נוספו הקיצורים get, post, put וכו' - שהם כתיבה מקוצרת ל <match... via:<method.
מעתה והלאה נעבוד רק איתם, אך כדאי לזכור שבמקור הם קיצור תחבירי ל match ושאת הפרטים על האופציות השונות הזמינות ל route - יש עדיין לחפש בתיעוד של match.

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

match 'products/:id', to: 'products#show', via: :get
match 'products/:id', controller: 'products', action: 'show', via: :get

אני מזכיר אותן, כי ייתכן שתתקלו בפרמטרים של to: ו controller: ב routes - ושתבינו את משמעותם / מקורם.

בקיצור, היום נכתוב את ה route הזה באופן הבא:

get 'products/:id' => 'products#show'

משמעות ההגדרה הזו היא:
אם יש קריאת GET עם URL המתאים לדפוס "<products/<x", קרא ל controller בשם ProductsController (שנמצא בתיקיה app/controllers/) ולמתודה בשם show, ושלח כארגומנט את המחרוזת x כערך של הפרמטר "id:".
את הערך ניתן לקרוא בתוך ה controller בעזרת המתודה params, למשל:

params[:id] # string "x"


כיצד זה עובד?


הנה דוגמת קוד מינימלית לשימוש ב routing, controller, ו view:



  • בקובץ ה routes.rb, הגדרנו route בסיסי - שאתם אמורים כבר להבין
  • ה Controller (הקטן ביותר האפשרי, בערך) מכיל מתודה (= action) בשם show שאליה ה route שהגדרנו יפנה. היא מחפשת במודל את המוצר ושומרת אותו כ product@.
  • כשה Controller יוצר את ה View, ריילס באופן "פלאי" (הסבר) מעתיק את ה instance variables, על ערכיהם, מה Controller (כדוגמת product@) ל View. משם, ניתן לגשת לשדות השונים בתוך ה product.
  • שימו לב לפקודה בשם link_to אותה תראו הרבה ב views של ריילס. היא מייצרת עבורנו link עם הכותרת שהגדרנו ("Show Details") לפעולה מסוימת של ה Controller. כיצד זה עובד? - נסביר בהמשך.


אופציות מתקדמות יותר להגדרת routes


Segment Keys אופציונליים
ממש כמו optional parameters בפונקציה, יש גם Segment Keys אופציונליים ב route. למשל:

get 'products/:id(/:facet)' => 'products#show'

יתאים ל 2 צורות של url, למשל:

http://site.com/products/441
http://site.com/products/441/specifications
במקרה הראשון יהיה ערך רק לפרמטר id: (הערך = '441'), ו facet: יהיה nil.
במקרה השני facet: יהיה שווה 'specifications'

שימו לב לצורה הנפוצה הבאה:

get 'products/:id(.:format)' => 'products#show'

המשמעות שלה היא matching ל url מהנוסח הבא:

http://site.com/products/441.json

כאשר הפרמטר format: מקבל את הערך 'json'.
הנקודה שליד שם הפרמטר מתארת את חלק מה Path.

ספציפית לגבי format:, זהו פרמטר עם התנהגות מיוחדת: פקודת respond_to שבשימוש בתוך ה controllers בודקת את הערך שלו, ולפיו מחליטה כיצד לפעול (האם להחזיר HTML או json - בד"כ).


Redirect
ניתן להגדיר route שיבצע redirect ל URL אחר ברשת. למשל:

get 'products/:id', to: redirect('v2/products/:id')

המילה to: מגיעה מתוך התחביר הישן של match שהזכרנו למעלה.
הערך של to: הוא Rack Endpoint שיכול להיות קוד inline (בשימוש בפונקציות lambda או proc) או שם של אפליקציית Rack אחרת (שיושבת בתיקייה app/metal/), או סתם URL אחר (כמו במקרה שלנו).


Constraints
אופציה זו מאפשרת לנו להציג תנאים נוספים (בדמות RegEx) על ה matching של ה route. למשל:

get 'products/:id' => 'products#show', constraints: {:id => /\d+/}

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

get 'products/:id' => 'products#show', id: /\d+/

ניתן להשתמש ב constraints על מנת לעשות בדיקת קלט למשתמש - אך זה לא מומלץ!
הכלי הוא יחסית מוגבל, וקובץ ה routing הוא לא בהכרח המקום הנכון לעשות זאת.
הכלל המנחה הוא להוסיף constraint על route, אם יש לכם route אחר שיתאים במקום. למשל:

get 'products/:id' => 'v2/products#show', id: /\d+/
get 'products/:id' => 'v1/products#show'


כלומר: בעבר קיבלתי מוצרים עם id: מכל סוג, ומתישהו (v2) הגבלתי אותם רק למספרים.
אם מופיע בקשה עם id: שאינו מספר - שלח אותה לטיפול ב controller הישן.

ל constraints יש גמישות נוספת מעבר לבדיקת ערכי segment keys, ניתן לקרוא עוד בנושא בפוסט הבא.


Wildcard Segment
אם מבנה ה URL מכיל מידע, ויכול להופיע בווריאציות שונות, ניתן להשתמש ב wildcard segment. כלומר, ה route:
get 'products/*other' => 'products#show'
יתאים ל URL כמו:

http://site.com/products/my/id/366/type/special/

וישלח ל controller משתנה בשם other: שערכו הוא 'my/id/366/type/special'.

ניתן להרכיב Wildcard Segments, עם Segment Keys רגילים. אם אתם זקוקים לעוד מידע בנושא חפשו את המונחים Wildcard Segment או Route Globbing.



רשימת יכולות שכיסינו כאן כנראה מכסה את רוב השימושים הנפוצים.
ניתן לקרוא בתיעוד הרשמי, Rails Routing from the Outside In, על אפשרויות נוספות.






Named Routes


מנגנון ה Named Routes בא לפשט (אפילו יותר) את העבודה עם routes.

כאשר נותנים ל route את השם "abc", ייווצרו שתי מתודות הזמינות לשימוש ב Controllers וה Views: אחת בשם abc_url, והשנייה abc_path.
  • המתודה abc_url היא תייצר url מלא שיתמפה ל route שהגדרנו.
  • המתודה abc_path תייצר את את חלק ה path של ה url, בלי protocol/host/port.

מנגנון ה Routing של ריילס בעצם משמש לשני כיוונים: גם לפענוח URL והתאמתו ל route (ומשם ל controller#action), וגם לצורך generation של URL (או path) שיוביל ל route שהוגדר.

הנה דוגמה:

get 'products/:id' => 'products#show', as: :show_product


תיצור את 2 המתודות show_product_url ו show_product_path על אובייקט ה app שזמין ל controllers וה views.

עכשיו אפשר ליצור קיצור ל path הזה מתוך אחד ה views בעזרת פקודת link_to. הפקודה, בגרסתה הארוכה, מקבלת hash כפרמטר:

link_to "הצג מוצר",
  controller: "products",
  action: "show",
  id: 12

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

link_to "הצג מוצר", show_product_url(id: 12)

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


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

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

link_to "הצג מוצר", show_product_url(12)

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

link_to "הצג מוצר", show_product_url(product)

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


עבור debugging, ניתן להריץ את הפונקציות xxx_url/xxx_path אובייקט ה app (מבלי להריץ את ה view). למשל:

app.show_product_path(12) # '/products/12'



שאלה של סגנון
מה ההבדל בין הפונקציה show_product_url לפונקציה show_product_path? מתי יש להשתמש בכל אחת מהן?

זה בעיקר עניין של סגנון:
  • show_product_url מייצרת Fully Qualified URL (כלומר: URL מלא ועצמאי), כפי שנדרש בתקן ה HTTP בפעולות redirect.
  • show_product_path, מייצרת URL רלטיבי שהדפדפן ידע להפוך אותו למלא בעת הצורך. ה URL קצר יותר ולכן נוח יותר לעבוד עם קבצי ה html שנוצרו. 
אז מה אתם מעדיפים? להיות דקדקנים (url, לעבוד ע"פ התקן) או לא לכתוב קצר (path, הדרך של ריילס)? עניין שלכם.
אני בד"כ מעדיף להיות דקדקן, אבל גם לפני ריילס עבדתי עם URLs יחסיים - וכך נראה לי שאמשיך.

בכל מקרה, כדאי להכיר של helper functions יש עלות של ביצועים. בזמן ריצה הן מחפשות בטבלת ה routing מה שלוקח זמן. קריאה ל link_to עם controller ו action - היא אפילו יותר יקרה.






Scoping


scope הוא מנגנון שעוזר לארגן את ה routes בקובץ ה routes.rb, ולחסוך כמה שורות קוד על הדרך. יש לו הרבה מאוד וריאציות של קיצור - אציג כמה מהעיקריות שבהן.
  # original
  get 'drivers/new' => 'drivers#new', as: :driver_new
  get 'drivers/edit/:id' => 'drivers#edit', as: :driver_edit
  post 'drivers/reassign/:id' => 'drivers#reassign', as: :driver_reassign

  # scope - DRY a bit with controller (1)
  # alternatives: "controller :drivers do ...", "scope :driver do"
  scope controller: :drivers do
    get 'drivers/new' => :new, as: :driver_new
    get 'drivers/edit/:id' => :edit, as: :driver_edit
    post 'drivers/reassign/:id' => :reassign, as: :driver_reassign
  end

  # scope - DRY a bit more with path (2)
  scope path: '/drivers', controller: :drivers do
    get 'new' => :new, as: :driver_new
    get 'edit/:id' => :edit, as: :driver_edit
    post 'reassign/:id' => :reassign, as: :driver_reassign
  end

  # scope - DRY a bit further with default params (3)
  scope '/drivers', controller: :drivers, as: 'driver' do
    get 'new' => :new, as: 'new'
    get 'edit/:id' => :edit, as: 'edit'
    post 'reassign/:id' => :reassign, as: 'reassign'
  end
במקור היו לנו 3 routes שהיינו צריכים להקליד לכל אחד כמה וכמה תווים...
  1. הגדרנו, בעזרת הפקודה scope, לכולם controller אחיד, וכך קיצרנו את הגדרות ה controller#action לשם ה action בלבד. יש עוד 2 דרכים להגיע לתחביר המקוצר הזה (בהערה, השני יכול לבלבל).
  2. הגדרנו path, שמקצר את חלק ה relative URL ב route.
  3. הגדרנו as, שמהווה prefix ל as: של ה routes הספציפיים, והצבנו את ה path כארגומנט הראשון לפונקציה scope, מה שמאפשר לנו לוותר את המפתח path:.
  4. אפשר לוותר על כל המופעים של אותיות a ו e - וריילס ישלים אותם בעצמו על סמך מילון מוכן מראש. סתאאםם.
אם אתם רואים את התוצאה הסופית וחושבים שניתן היה לקצר אותה יותר - אתם צודקים.

עוד פרמטר פופולרי של scope הוא module: :abc שמניח ש:
  1. ה Controller שייך ל Module (שפת רובי) בשם Abc. כלומר Abc::SomeController.
  2. => ה Controller נמצא בתת תיקיה בשם abc/

ישנה פונקציית קיצור בשם namespace שפועלת כך:
  namespace :drivers do
    # routes
  end

  # syntactic sugar of / equivalent to writing:
  scope path: '/drivers', module: :drivers, as: 'driver' do
    # routes
  end



הקיצור האולטימטיבי ליצירת routes


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

כחלק מההתאמה של ריילס למודל ה REST, הוגדר ל Controller סט ה Actions הגנרי הבא:
  • index - הצגת רשימה של אובייקטים
  • create - יצירת אובייקט חדש (מתוך פעולת POST)
  • new - החזרת template ליצירת אובייקט חדש (ללא יצירת האובייקט בפועל). בד"כ מדובר בהחזרת טופס וובי, "יצירת אובייקט חדש", למשתמש.
  • show - הצגת הפרטים של אובייקט מסוים
  • update - עדכון אובייקט מסוים
  • edit - החזרת template לעדכון אובייקט חדש. בד"כ מדובר בהחזרת טופס וובי "עדכון" אובייקט שבעקבותיו תגיע פעולת update.
  • destroy - מחיקת אובייקט מסוים

הפקודה הבאה, תמפה סדרה של routes עבור שבע הפעולות:
resources :products

# syntactic sugar of / equivalent to writing:
get 'products(.:format)' => 'products#index', as: :products # e.g. products_url()
post 'products(.:format)' => 'products#create',
get 'products/new(.:format)' => 'products#new', as: :new_product # e.g. new_product_url(12)
get 'products/:id/edit(.:format)' => 'products#edit', as: :edit_product # e.g. edit_product_url(12)
get 'products/:id(.:format)' => 'products#show', as: :product # e.g. product_url(12)
patch 'products/:id(.:format)' => 'products#update',
delete 'products/:id(.:format)' => 'products#delete'

למיטב הבנתי, ה named routes נוצר רק לפעולות ה get, מכיוון שרק לפעולות אלו ניתן לעשות re-direct בפדפדפן.

שימו לב שאם יש לנו route כמו הבא, שמופיע אחרי שורת ה resources:

get 'products/poll' => 'products#poll'

לעולם לא יגיעו אליו. ריילס תתאים אותו ל products/:id שנוצר בעקבות פקודת ה resources. הדרך היחידה שיוכלו להגיע אליו הוא אם נציב את ה route הזה לפני פקודת ה resources. ההתאמה ל routes נעשית בסדר שבו הם מופיעים בקובץ.

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

resources :products, only: [:index, :show, :edit]

או השתמשו בפקודה ההופכית: except:.







קינון routes


קינון של routes מתבצע לרוב בין שני אובייקטים (REST resources) שיש בניהם קשר של שייכות. דוגמה שחוקה: כתבה, והערות לכתבה. מהות הקשר שמומלץ לנהל עבורו routes מקוננים הוא קשר "חזק" של הרכבה (composition), כאשר לאובייקט התלוי (למשל הערה) אין משמעות ללא אובייקט האב. למשל: כאשר מוחקים כתבה, טבעי שמוחקים את ההערות שלה. לא טבעי יהיה, בד"כ, למחוק את אובייקט המחבר - וזו אינדיקציה טובה שלא כדאי לקנן את המחבר תחת כתבה, אפילו שיש ביניהם קשר.

הנה דוגמה לקינון:
resources :articles do
  resources :comments
end

# generates 7 routes for articles + routes such as:
  get 'articles/:article_id/comments' => 'comments#show'
  get 'articles/:article_id/comments/new' => 'comments#new'
  put 'articles/:article_id/comments/:id' => 'comments#update'
... את הרשימה המלאה של ה routes הנוספים שנוצרים ניתן להסיק או לבדוק את הרשימה בתיעוד הרשמי.
כמובן שה routes של article יפנו ל ArticlesController וה routes המקוננים יפנו ל CommentsController. הקינון, חוץ מזה שהוא נוח מבחינת מידול ה REST, מאפשר ל CommentsController לקבל גם את ה article_id:.

עוד כלל הוא לעולם לא לעשות nesting עמוק (כלומר: 2

הנה עוד 2 כלים נפוצים, member ו collection, המאפשרים להוסיף פעולות חדשות:
resources :articles do
  member do
    get :preview
  end
  collection do
    get :search
  end
end

member מוסיף תחת ה resource עוד פעולה, אולי כזו שלא מוגדרת ב default, למשל preview. הפעולה היא כזו שמוגדרת על אובייקט יחיד - ולכן יש צורך במזהה. למשל, במקרה הנ"ל ייווצר ה route הבא:

/articles/:id/:preview

collection הוא דומה מאוד, אבל מגדיר פעולה על סט ה resources, כזו שלא צריכה מזהה. בדוגמה הנ"ל:

/articles/search



בדיקת ה routes בפועל

כדי לבדוק שה routes נכונים, יש בריילס 2 כלים שימושיים:

  • מ command line, בעזרת הפקודה rake routes. ניתן לבדוק Controller ממוקד ע"י שימוש בפרמטר, למשל: rake routes CONTROLLER=articles.
  • כאשר השרת רץ (rails s), ניתן לראות את רשימת ה routes תחת ה path הבא: rails/info/routes/




סיכום


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

כפי שאתם רואים אני עוד שקוע ריילס - ויש עוד כברת דרך....


שיהיה בהצלחה!




-----

לינקים רלוונטיים

פיצול ה routes לקבצים שונים


-----

[א] ה route:

get 'some_path/:a/:b/:c' => ...

יקבל ערכים זהים עבור 2 ה urls הבאים:

/some_path/xx/yy/zz
/some_path/xx?a=yy&b=zz