יצירת מודלים של ישויות

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

  • יוצרים מחלקה של מודל שמגדירה את מאפייני הישות.
  • יוצרים מחלקה של מודל expando שלא מגדירה מראש ישויות.

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

יצירת מחלקת מודל עם מאפיינים

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

from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

כאשר רוצים שלכל ישות Account יהיו מאפיינים של שם משתמש, מזהה משתמש וכתובת אימייל.

רשימה מלאה של סוגי הנכסים זמינה בחומר העזר בנושא נכסי ישויות.

יצירת מחלקת מודל Expando

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

כך יוצרים מודל Expando:

class Mine(ndb.Expando):
    pass
...
e = Mine()
e.foo = 1
e.bar = 'blah'
e.tags = ['exp', 'and', 'oh']
e.put()

הפעולה הזו כותבת ישות למאגר הנתונים עם מאפיין foo עם ערך שלם 1, מאפיין bar עם ערך מחרוזת 'blah' ומאפיין חוזר tag עם ערכי מחרוזת 'exp',‏ 'and' ו-'oh'. המאפיינים עוברים אינדוקס, ואפשר לבדוק אותם באמצעות המאפיין _properties של הישות:

return e._properties
# {
#     'foo': GenericProperty('foo'),
#     'bar': GenericProperty('bar'),
#     'tags': GenericProperty('tags', repeated=True)
# }

ל-Expando שנוצר על ידי קבלת ערך ממאגר הנתונים יש מאפיינים לכל ערכי המאפיינים שנשמרו במאגר הנתונים.

אפליקציה יכולה להוסיף מאפיינים מוגדרים מראש לExpando מחלקת משנה:

class FlexEmployee(ndb.Expando):
    name = ndb.StringProperty()
    age = ndb.IntegerProperty()
...
employee = FlexEmployee(name='Sandy', location='SF')

כך מקבלים את employee עם מאפיין name עם הערך 'Sandy', מאפיין age עם הערך None ומאפיין דינמי location עם הערך 'SF'.

כדי ליצור מחלקת משנה Expando שהמאפיינים שלה לא עוברים אינדוקס, מגדירים את _default_indexed = False בהגדרת מחלקת המשנה:

class Specialized(ndb.Expando):
    _default_indexed = False
...
e = Specialized(foo='a', bar=['b'])
return e._properties
# {
#     'foo': GenericProperty('foo', indexed=False),
#     'bar': GenericProperty('bar', indexed=False, repeated=True)
# }

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

טכניקה שימושית נוספת היא שליחת שאילתה מסוג Expando לנכס דינמי. שאילתה כמו הבאה

FlexEmployee.query(FlexEmployee.location == 'SF')

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

FlexEmployee.query(ndb.GenericProperty('location') == 'SF')

שימוש ב-Model Hooks

‫NDB מציע מנגנון קל משקל של hooking. על ידי הגדרת hook, אפליקציה יכולה להריץ קוד לפני או אחרי סוג מסוים של פעולות. לדוגמה, Model יכול להריץ פונקציה לפני כל get().

פונקציית hook מופעלת כשמשתמשים בגרסאות הסינכרוניות, האסינכרוניות והמרובות של השיטה המתאימה. לדוגמה, וו 'pre-get' יחול על כל הפעולות get(), get_async() ו-get_multi(). לכל וו יש גרסאות לפני RPC ואחרי RPC.

ה-Hooks יכולים לעזור במקרים הבאים:

  • שמירה במטמון של שאילתות
  • ביקורת פעילות ב-Cloud Datastore לכל משתמש
  • חיקוי של טריגרים במסד נתונים

בדוגמה הבאה אפשר לראות איך מגדירים פונקציות hook:

from google.appengine.ext import ndb
...
class Friend(ndb.Model):
    name = ndb.StringProperty()

    def _pre_put_hook(self):
        _notify('Gee wiz I have a new friend!')

    @classmethod
    def _post_delete_hook(cls, key, future):
        _notify('I have found occasion to rethink our friendship.')
...
f = Friend()
f.name = 'Carole King'
f.put()  # _pre_put_hook is called
fut = f.key.delete_async()  # _post_delete_hook not yet called
fut.get_result()  # _post_delete_hook is called

אם משתמשים ב-post-hooks עם ממשקי API אסינכרוניים, ההפעלה של ה-hooks מתבצעת על ידי קריאה ל-check_result(), get_result() או על ידי החזרת ערך (בתוך tasklet) של future של שיטה אסינכרונית. ה-hooks של הפוסט לא בודקים אם ה-RPC הצליח. ה-hook פועל ללא קשר לכישלון.

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

העלאת חריגה במהלך פעולת pre-hook מונעת את ביצוע הבקשה. למרות שה-hooks מופעלים בתוך <var>*</var>_async methods, אי אפשר למנוע RPC על ידי העלאת tasklets.Return ב-hook שלפני ה-RPC.

המאמרים הבאים