Firebase

הקדמה


Firebase הוא סטאטראפ (שבהתחלה נקרא Envolve) שהוקם בשנת 2011 ע"י Andrew Lee ו- James Tamplin ונרכש ע"י גוגל ב-2014. מאז הרכישה, גוגל הוסיפה ל-Firebase המון שרותים נוספים שנותנים מענה לצרכים שונים לפיתוח מובייל ו-web. בפוסט הזה נתייחס ל-data base של Firebase.
Firebase נמצא בשימוש ביישומים רבים והוכיח את עצמו בעבודה עם כמויות גדולות מאוד של משתמשים.


אפליקציות שמשתמשות ב-Firebase (מתוך האתר הרשמי)

דבר ראשון שצריך לדעת זה ש-Firebase הוא מסד נתונים מסוג NoSQL. בגדול יש 4 סוגים של NoSQL (שהם Key/Value, Document, Column, Graph), ו-Firebase הוא מסוג Key/Value אבל גם דומה ל-Document DB. למרות שלרוב היישומים מתאים מסד נתונים רלציוני (כמו SQL), ישנם לא מעט יישומים שמסד נתונים רלציוני לא אידאלי עבורם. ולכן אם אתם מכירים רק מסדי נתונים רלציונים כדאי מאוד להכיר גם מסדי נתונים שאינם רלציונים וכך להתאים לכל יישום את מסד הנתונים המתאים. 
  • היתרון של מסד נתונים מסוג NoSQL הוא ב-Scaling. הוא יכול יחסית בקלות לפצל את העבודה על כמה מכונות. המשמעות היא שמסד נתונים כזה יכול לטפל בכמות רבה יותר של נתונים מאשר מסד נתונים רלציוני. 
  • החיסרון שלו הוא במה שנקרא eventual consistency, פרוש הדבר הוא שבסופו של דבר המידע יהיה קונססיטנטי, אבל זה לא מיידי וזה עלול לקחת קצת זמן, ולכן לפעמים עלול להיות מצב שנקבל מידע שגוי כי מסד הנתונים עוד לא הספיק להתעדכן. זה מצב די נדיר אבל קורה, במיוחד שיש הרבה משתמשים שפונים בו זמנית. וצריך להכיר את זה ולהחליט אם זה מתאים למערכת שלנו. 
  • הדבר החשוב ביותר כשמשתמשים ב-Firebase זה לתכנן נכון את הדרך שבא נשמר המידע. ולצורך כך צריך להבין טוב איך Firebase עובד. 
  • Firebase הוא real time data base, כך שאם היישום שלך צריך מידע ב-real time אז Firebase יכול להיות בחירה טובה עבורך. במילים real time אני מתכוון שברגע שאני מבקש נתון מסוים אני בעצם מתחיל להאזין (listener) לאותו נתון. כך שאם הנתון משתנה Firebase ישלח לי event שהנתון הזה השתנה, ואני לא צריך לנחש מתי הנתון ישתנה ולבדוק כל פעם בעצמי. כמובן, שגם אם אני לא צריך מסד נתונים real time, עדיין Firebase יכול להתאים לי.
  • באתר של Firebase יש דוקומנטציה טובה שמאפשרת הבנה עמוקה של המוצר. 
  • ל-Firebase יש תוכנית חינמית כך שניתן להשתמש בו לכל מיני ניסויים ולמידה ללא עלות.
  • Firebase עובד עם כל סוגי ה-frontend וה-backend כך שלא משנה באיזה טכנולוגיה אתה משתמש לכתיבת היישום שלך, כיון ש-Firebase מספק REST API, קרוב לוודאי שהוא יתאים גם לשימוש שלך.
  • Firebase מאוחסן בשבילך. כך שאתה לא צריך לקנות שרת ולהתקים עליו Firebase. מצד שני צריך לשלם ל-Firebase  עבור זה אבל רק בשלב שהגעת לנקודה מסוימת שמצריכה תשלום. למזלנו, יש המון שרותים ש-Firebase  מציע בחינם והנקודה שבה Firebase דורש תשלום מספיקה להרבה אפליקציות להשתמש ב-Firebase  בחינם.
  • הנתונים ב-Firebase נגישים בעזרת URL (מה שנקרא URL oriented). ה-URL של כל נתון נגזר ישירות ממבנה הנתונים שיצרנו.

יצירת פרויקט חדש, קריאת נתונים

ניצור משתמש באתר של Firebase (זה די קל ומהיר אם אתה מחובר לחשבון גוגל שלך) ואז נלחץ על Add Project. בסרגל האפשרויות בצד שמאל נלחץ על Authentication ואז נלחץ על הכפתור בפינה הימנית העליונה שנקרא WEB SETUP. יפתח לנו חלון שבו יש את כל הקוד שנדרש לנו כדי להכניס Firebase לפרויקט שלנו. נעתיק את הקוד הזה לתוך קובץ ה-index.html שלנו.
נלחץ בסרגל האפשרויות בצד שמאל על Database ואז בסרגל העליון נלחץ על RULES. לצורך הלימוד נסיר את האבטחה ונשנה את מה שכתוב שם ל:


1
2
3
4
5
6
{
  "rules": {
    ".read": true,
    ".write": true
  }
}


נחזור בסרגל העליון ל-DATA, נעמוד על הפרויקט שלנו ונלחץ על סימן הפלוס וניצור שדה חדש בשם usrName, וניתן לו ערך של Yosi.


עכשיו נחזור לערוך את קובץ ה-HTML שלנו בצורה הבאה:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!doctype html>
<html>

<head>
  <title>Firebase learning</title>
</head>

<body>
  <h1 id="userName"></h1>

  <script src="https://www.gstatic.com/firebasejs/4.1.3/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: "AIzaSyBPx7bl1ZogSpAgnB6SgtNQ5fPl4tBPkXI",
      authDomain: "rating-project-ded62.firebaseapp.com",
      databaseURL: "https://rating-project-ded62.firebaseio.com",
      projectId: "rating-project-ded62",
      storageBucket: "rating-project-ded62.appspot.com",
      messagingSenderId: "474071235854"
    };
    firebase.initializeApp(config);

    var userName = document.getElementById("userName");
    var dbRef = firebase.database().ref().child('userName');
    dbRef.on('value',snap => userName.innerText = snap.val());
  </script>

</body>

</html>


הוספנו את השורות המסומנות. 
שורה 24 פשוטה. 
בשורה 25 יצרנו משתנה שמצביע על השדה שנקרא userName ב-DB שלנו. 
בשורה 26 השתמשנו בפקודה on שזו פקודה של Firebase. הפקודה הזו יוצרת listener על השדה userName. הפרמטר הראשון אומר שאנחנו רוצים להאזין ל-event שנקרא value. ה-event הזה נשלח בכל פעם שיש איזשהו שינוי ב-path שעליו אנחנו מאזינים.
הפרמטר השני זו פונקציית callback שמקבלת כ-input את המידע מ-Firebase שנקרא בשם snap (מלשון snapshot) והיא מכניסה את הערך של השדה userName לתוך התג h1 בקובץ html שלנו (שורה 9).
עכשיו נבדוק את מה שעשינו. נפתח את דף ה-html שלנו ונשנה ב-Firebase את הערך של השדה userName. ונראה שכל שינוי שאנחנו עושים מיד מעדכן את המידע בדפדפן.

כתיבת נתונים

עכשיו נכתוב פרויקט פשוט שמכניס נתונים ל-DB. 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!doctype html>
<html>

<head>
  <title>Firebase learning</title>
</head>

<body>

  <script src="https://www.gstatic.com/firebasejs/4.1.3/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: "AIzaSyBPx7bl1ZogSpAgnB6SgtNQ5fPl4tBPkXI",      
      authDomain: "rating-project-ded62.firebaseapp.com",
      databaseURL: "https://rating-project-ded62.firebaseio.com",
      projectId: "rating-project-ded62",
      storageBucket: "rating-project-ded62.appspot.com",
      messagingSenderId: "474071235854"    };
    firebase.initializeApp(config);

    var dbRef = firebase.database().ref().child('userName');
    
    function addUser(newUser){
      dbRef.push({
        userName: newUser
      });
    }
  </script>
<input id="newUser">
<button onclick="document.getElementById('userName').innerText = 
                 addUser(document.getElementById('newUser').value)">add user</button>

</body>

</html>


בקוד הזה השתמשנו בפקודה של Firebase שנקראת push. וכל פעם הכנסנו עוד מידע לתוך השדה newUser. שם המשתמש החדש נלקח מתוך תיבת הטקסט.
נלחץ על קובץ ה-html שלנו ונקבל דף כזה:

נכניס בתיבת הטקסט שם ונלחץ על הכפתור add user. נוכל לראות שכל לחיצה על הכפתור מוסיפה עוד משתמש ב-DB.

ניתן לראות שכל ערך שהכנסתי לשדה userName קיבל אוטומטית ID מ-Firebase.

הבנת מבנה הנתונים

מערכים

קודם כל צריך להבין שהמערכים ב-Firebase לא דומים למערכים ב-JavaScript. ב-JavaScript לכל איבר במערך יש אינדקס לפי מספר סידורי עולה (0,1,2,3). ב-Firebase כל איבר במערך מקבל ID ייחודי אוטומטי מ-Firebase ודרך ה-ID הזה ניתן לגשת לאותו איבר.

גישה לנתון שלא קיים

במקרה שמנסים לגשת למידע (כמובן דרך כתובת URL) שלא קיים נקבל בחזרה את הערך null.
לדוגמה, במסד נתונים שיצרתי לעיל יש רק שדה שנקרא userName וה-URL שלו הוא:

https://console.firebase.google.com/project/rating-project-ded62/database/data/userName

אם אני אנסה לגשת לשדה שלא קיים ונקרא userPhone דרך אותו URL רק שאשנה את הסיומת ל-userPhone בצורה הבאה:

https://console.firebase.google.com/project/rating-project-ded62/database/data/userPhone

Firebase יחזיר לי null. כמו שרואים בתמונה הבאה:

גישה דרך רפרנס (והפונקציות child, parent, root)

ב-Firebase הגישה לנתונים מתבצעת ע"י רפרנס שמקושר ל-URL מסוים. למשל במקרה שלנו אני יכול ליצור 2 רפרנסים:

1
2
var dbRef = firebase.database().ref();
var userRef = firebase.database().ref().child('userName');

הראשון מפנה לנקודה ההתחלתית של ה-DB (ה-root), והשני מפנה לשדה userName. עשינו שימוש בפונקציה child כדי לקבל רפרנס לשדה userName שנמצא מתחת לנקודה ההתחלתית של ה-DB.
ניתן להשתמש גם בפונקציה parent שמחזירה רפרנס של שדה אחד מעל השדה של הרפרנס הנוכחי. לדוגמה:



1
2
var userRef = firebase.database().ref().child('userName');
var dbRef = userRef.parent;

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


1
2
var userRef = firebase.database().ref().child('userName');
var dbRef = userRef.root;

מבנה שטוח ולא מבנה עמוק

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

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

קריאת נתונים

ניתן לקרוא נתונים מ-Firebase ע"י שימוש בפונקציה on או בפונקציה once. ההבדל ביניהם הוא שהפונקציה on מאזינה לנתונים ומקבלת event על כל שינוי בנתונים. ואילו הפונקציה once קוראת את הנתונים בצורה חד פעמית.


קריאה בעזרת הפונקציה on

כפי שכבר ראינו לעיל, צורת השימוש בפונקציה on היא:



1
2
var userRef = firebase.database().ref().child('userName');
userRef.on('value',snap => userName.innerText = snap.val());

הפונקציה on מקבלת 2 פרמטרים: 
הפרמטר הראשון, זה על איזה event להאזין, ברוב המקרים נשתמש ב-'value' כי אנחנו רוצים לקבל event על כל שינוי במידע שעליו אנחנו מאזינים.
הפרמטר השני, זו פונקציית callback שמקבלת כ-input את המידע שהגיע מ-Firebase (מקובל לקרוא למידע הזה snap כי זה snapshot של המידע ברגע הנוכחי) ועושה מה שאנחנו צריכים עם המידע שהתקבל.

קריאה בעזרת הפונקציה once

השימוש בפונקציה once דומה לשימוש בפונקציה on. 

1
2
var userRef = firebase.database().ref().child('userName');
userRef.once('value',snap => userName.innerText = snap.val());

ההבדל הוא שה-event יגיע אלינו רק פעם אחת. צריך לדעת שה-event הזה שנקרא value לא נשלח אלינו רק כשיש שינוי אלא גם בהתחלה הוא ישלח אלינו עם הערך ההתחלתי.

הפסקת ההאזנה לנתונים

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


1
2
3
4
5
6
7
8
var userRef = firebase.database().ref().child('userName');
userHandle = userRef.on('value',snap => userName.innerText = snap.val());
...
...
...
if(typeof userRef === 'object' && typeof userHandle) {
   userRef.off('value', userHandle);
}

כתיבת נתונים

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

ואת הנתונים האלו שלחתי ל-Firebase. 
להלן הקוד השלם:



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!doctype html>
<html>

<head>
  <title>Firebase learning</title>
</head>

<body>

  <script src="https://www.gstatic.com/firebasejs/4.1.3/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: "AIzaSyBPx7bl1ZogSpAgnB6SgtNQ5fPl4tBPkXI",      
      authDomain: "rating-project-ded62.firebaseapp.com",
      databaseURL: "https://rating-project-ded62.firebaseio.com",
      projectId: "rating-project-ded62",
      storageBucket: "rating-project-ded62.appspot.com",
      messagingSenderId: "474071235854"    };
    firebase.initializeApp(config);
   
    function addReview(userName, cityName, reviewText, numberOfStars) {
      var userRef = firebase.database().ref().child(cityName)
                     .child('review').child('users').child(userName);
      userRef.push({
        description: reviewText,
        stars: numberOfStars      
      });
    }

  </script>
  <div dir="rtl">
    <div>
      שם:
      <input id="userName">
    </div>
    <div>עיר:
      <input id="cityName">
    </div>
    <div>ביקורת:
      <input id="reviewText">
    </div>
    <div>דרוג:
      <input id="numberOfStars">
    </div>

    <button onclick="addReview(document.getElementById('userName').value, 
                         document.getElementById('cityName').value,
                         document.getElementById('reviewText').value,
                         document.getElementById('numberOfStars').value)">שלח</button>
  </div>

</body>

</html>

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


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

במקרה שמכניס הביקורת הוא משתמש קיים, כמו דוד:




Firebase יכניס את הביקורת החדשה תחת המשתמש הקיים:

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

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



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

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

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



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


נעדכן את הפונקציה  addReview בקוד הבא:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    var dbRef = firebase.database().ref();

    function addReview(userName, cityName, reviewText, numberOfStars) {
      var usersRef = firebase.database().ref().child(cityName).child('review')
                     .child('users').child(userName);
      var newCity = true;

      usersRef.push({
        description: reviewText,
        stars: numberOfStars
      }, function (err) {
        if (err) {
          console.warn('error: ', err);
        } else {
          dbRef.child('ReviewedCities').once('value', function (snap) {
            var cities = snap.val();
            Object.keys(cities).forEach(function (key, index) {
              if (cities[key].name === cityName) {
                newCity = false;
                return;
              }
            });
            if (newCity == true)
              dbRef.child('ReviewedCities').push({ name: cityName });
          });
        }
      });
    }

הסבר:
הפונקציה push יכולה לקבל פרמטר שני. הפרמטר הזה הוא פונקציית callback למקרה שיש error. 
בשורה 11 הוספנו את פונקציית ה-callback ובדקנו אם קיים error. 
במקרה שאין error הגענו לשורה 15 ושם קראנו באופן חד פעמי את הצומת ReviewedCities. בשורה 17 הכנסנו לולאה שעוברת על כל השדות של ReviewedCities ובודקת אם יש שם אובייקט שמכיל בשדה 'name' את השם של העיר החדשה (במקרה שלנו - חיפה). במקרה שמצאנו אובייקט כזה שמכיל את חיפה אנחנו מפסיקים את הלולאה ע"י return ומכניסים למשתנה newCity את הערך true.
בשורה 23, לאחר הלולאה, אנחנו בודקים את הערך של newCity במקרה שהוא true אנחנו מכניסים את שם העיר החדשה לתוך ReviewedCities. 

והתוצאה שנקבל היא:



עדכון שדה ע"י הפונקציה update

כפי שראינו, יש לנו שדה של ממוצע הדרוג שהעיר קיבלה:




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

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

הביקורת הזו תשנה את הממוצע מ-3.5 ל-4. כדי לגרום לשדה avarage להתעדכן, נעדכן את הפונקציה addReview בצורה הבאה:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var dbRef = firebase.database().ref();

function addReview(userName, cityName, reviewText, numberOfStars) {
  var usersRef = dbRef.child(cityName).child('review').child('users').child(userName);
  var cityReviewRef = dbRef.child(cityName).child('review');
  var newCity = true;
  usersRef.push( {
 description: reviewText,
 stars: numberOfStars
  }, function (err) {
 if (err) {
   console.warn('error: ', err);
 } else {
   dbRef.child('ReviewedCities').once('value', function (snap) {
  var cities = snap.val();
  Object.keys(cities).forEach(function (key, index) {
    if (cities[key].name === cityName) {
   newCity = false;
   return;
    }
  });
  if (newCity == true)
    dbRef.child('ReviewedCities').push({ name: cityName });
   });

   cityReviewRef.once('value', snap => {
  numOfReviewers = snap.child('users').numChildren();
  startsAverage = ( (numOfReviewers-1) * snap.child('avarage').val() 
    + Number(numberOfStars)) / numOfReviewers;
  cityReviewRef.update({avarage: startsAverage});
   });
   
 }
  });
}


הסבר על תוספת הקוד המודגש:
כדי לעדכן את ממוצע הדרוג צריך לקחת את הממוצע הנוכחי ולהכפיל אותו במספר המדרגים (לא כולל את המדרג החדש), במקרה שלנו ממוצע הדרוג היה 3.5 ומספר המדרגים 2. המכפלה היא 7. למספר הזה נוסיף את הדרוג החדש (5) ונחלק במספר המדרגים החדש (3) כדי לקבל ממוצע. במקרה שלנו
 ( 7 + 5 ) / 3 = 4
לכן, הקוד שלנו מתחיל בשורה 26 שבה אנחנו קוראים מה-DB את שדה ה-review של אותה העיר שקיבלה עכשיו ביקורת חדשה, כדי לקחת משם את הערך של השדה avarage ואת מספר המדרגים.
כדי לדעת את מספר המדרגים השתמשנו בפונקציה numCildren. זו פונקציה של Firebase שמחזירה את מספר הילדים שיש לאותה צומת שעליה הפעלנו את הפונקציה.
המספר הזה כבר כולל את הביקורת החדשה, ולכן בשורה 28 הפחתנו ממנו 1 כדי לממש את החישוב של הממוצע החדש כפי שהסברנו לעיל. 
המשתנה numberOfStars מכיל את הדרוג החדש שהמדרג נתן. והפעלנו עליו את פונקציית Number כדי להמיר אותו ממחרוזת למספר.
בשורה 30, מעדכנים את השדה avarage בממוצע הדרוג שכולל את הביקורת החדשה, ע"י פונקציית update.
התוצאה שתתקבל היא:




5 תגובות:

  1. אנונימי1/2/18 08:53

    תודה רבה!
    חבל שלא פגשתי במאמר הזה לפני ששברתי את הראש על להבין את פיירבייס לבד..

    השבמחק
    תשובות
    1. שמח לשמוע שהמאמר עזר

      מחק
    2. שלום רפי המאמר עוזר מאד
      תודה רבה על הבלוג
      מדוע כשאני עושה את הדוגמא השניה של הHTML הוא עובד לי מצוין ומכניס ערכים לתוך הDB
      אבל הדוגמא הראשונה נותנות לי תוצאה של [object Object] בבדפדפן כבר בהרצה הראשונה (לפני שיש שינויים בדאטה)
      איך אני יכול לדבג את זה
      תודה רבה

      מחק
    3. אנונימי11/9/23 20:34

      תעשה לזה parsing.
      נראה שהערך שאתה מנסה למשוך הוא אובייקט [אתה מושך ערך של צומת ולא של קצה]

      מחק
  2. לפרוייקט בשלב מתקדם דרוש מומחה
    עם ניסיון ב..Fire base real time

    השבמחק