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

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

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

new data moduleמכאן נעבור לשינוי גדול יותר. נלך לתפריט הראשי ונבחר ב File. משם נבחר ב New … (כלומר יש 3 נקודות לאחר המילה new). יפתח לנו חלון בשם הפריט שבחרנו, בו תהיה לנו רשימה של כל מיני דברים שניתן ליצור עבור הפרוייקט שלנו.  נבחר באפשרות Data Module. יכול להיות שאצלכם יהיו פחות אפשרויות ממה שיש אצלי בתמונה וזה בסדר גמור (אצלי מותקנים תוספים נוספים ללזרוס).

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

לאחר שיצרנו את ה DataModule נקבל חלון לבן (או צבע אחר במידה והסיבבה שלכם עובדת עם צבעים אחרים) וריק. עכשיו אנחנו צריכים לגרום ל Data Module שלנו לפני הכל, להיות החלון הראשון שיווצר עבורינו כאשר התוכנה שלנו תרוץ. כמו כל דבר בלזרוס, גם כאן יש כמה דרכים לעשות את זה. נשתמש בתפריט Project ונבחר או לצפות בקוד או את הגדרות הקוד.project options

במידה ונבחר באפשרות של Project Options נלך ללשונית Forms ונשים את ה Data Module כחלון הראשון שיאותחל.

במידה ואנחנו מעוניינים לעשות את זה ידנית, נלחץ על View Source בתפריט Project ונשים את השורה

Application.CreateForm(TDataModule1, DataModule1);

מעל היצירה של TForm1 וכך נבצע את אותה הפעולה כמו ש Project Options מבצע.

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

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

נלך לקוד המקור של הData Module שיצרנו ונוסיף במחלקה של TDataModule1 פרוצדורה בשם commit_date:

procedure commit_data (DataSet : TDataSet);

ואז נלחץ CTRL+Shift+C ולזרוס יכניס אותו כבר לתוכן של הפונקציה ונכתוב את הקוד הבא בפנים:

procedure TDataModule1.commit_data (DataSet : TDataSet);
begin
  TSQLQuery(DataSet).ApplyUpdates;
  TSQLTransaction(TSQLQuery(DataSet).Transaction).CommitRetaining;
end;

הוא דומה למה שעשינו בחלק הקודם אבל עדיין לא זהה. הפעם הזו אנחנו דוגלים בשיטה של DRY וזה בעצם מה שהקוד שלנו עושה. אנחנו אומרים למחלקה TSQLQuery להשתמש ב instance שאותחל (אובייקט) ואיתו להריץ את הפקודה הראשונה שהיא כמובן ApplyUpdates. אחריה אנחנו משתמשים במחלקה TSQLTransaction עם ה instance שנמצא בתוך המחלקה TSQLQuery.Transaction וכמובן להריץ את CommitRetaining. הסיבה שאני מבצע את זה בצורה הזו ולא עם TSQLQuery(DataSet).Transaction בגלל שTransaction מכיל מחלקה כללית ש TSQLTransaction יורש ממנה וTSQLTRansaction מכיל את CommitRetaining.

עכשיו נחזור לחלון הגרפי של TDataModule (לחיצה על F12 מעורך הטקסט) ונלך ללשונית Events ב Object Inspector ונבחר כמובן ב TSQLQuery1 ובאירוע של AfterPost. לחיצה כפולה על האירוע וכתיבת הקוד הבא:

procedure TDataModule1.SQLQuery1AfterPost (DataSet : TDataSet );
begin
commit_data(DataSet);
end;

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

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

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

uses dmdata;

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

procedure TForm1.FormClose (Sender : TObject; var CloseAction : TCloseAction);
begin
  DataModule1.SQLQuery1.Close;
  DataModule1.IBConnection1.Close;
end;
procedure TForm1.FormCreate (Sender : TObject);
begin
  DataModule1.IBConnection1.Open;
  DataModule1.SQLQuery1.Open;
end;

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

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

CREATE TABLE posts
(
id      BIGINT PRIMARY KEY NOT NULL,
title     varchar(1024) DEFAULT '<untitled>' NOT NULL,
body   BLOB SUB_TYPE TEXT                         NOT NULL,
excerpt COMPUTED BY (SUBSTRING(body FROM 1 FOR 256)),
Visible  BOOLEAN     DEFAULT 0                      NOT NULL,
Created TIMESTAMP,
Modified TIMESTAMP
);

זוהי טבלה מיוחדת היות והיא משתמשת ב"טריק" בשביל ליצור עבורינו excerpt בצורה אוטומטית, שמחושבת מהטקסט שכתבנו. השדה ב firebird מוגדר כשדה computed by ואנחנו שומרים את ה255 תווים הראשונים של body. חשוב לציין ש"בחיים האמיתיים" לא נשתמש בפונקציה הזו או "בשדה מחושב" בשביל זה, אבל כרגע למדריך זה נבצע את הפעולה בצורה הזו. הסיבה שלא נעשה את זה ככה תמונה ב4 גורמים:

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

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

עכשיו ניגש לנו אל הDataModule ונוסיף את הטבלה החדשה עם השאילתא הבאה:

SELECT ID, TITLE, BODY, EXCERPT, VISIBLE, CREATED, MODIFIED FROM POSTS

fieldkindונבצע את אותן הפעולות שביצענו בחלק הקודם. כאשר בכל מה שקשור לשדות שאנחנו לא נעדכן ידנית נסיר את Required ונשים אותם כ Readonly.

שינוי שמות בלזרוסעכשיו אחרי שהוספנו עוד שאילתא, נגלה שמאוד קשה גם להסתדר פתאום עם 2 שאילתות כי לא בהכרח נזכור מה כל שאילתא, ולבדוק כל פעם מחדש את התוכן של ה SQL אינו הפתרון. הפתרון הוא לתת שמות לרכיבים בעלי משמעות. שימו לב שהשם חייב להיות תחת חוקי שמות המשתנים של פסקל ולכן לא כל שם יתקבל אם תחרגו מהחוקים. החוקים אומרים שהשם לא יכול להתחיל במספר, אבל הוא יכול להיות אלפאנומרי ועם קו תחתי ובאורך של עד 256 תווים כאשר אין הבדל בין אות קטנה לגדולה. לשאילתא הראשונה אתן את השם tblAuthors כלומר טבלת Authors. אשנה את השם בתכונה Name (בכל רכיב בלזרוס זה נעשה בתכונה הזו).  לאחר שנשנה את השם, נהיה צריכים לשנות את השם גם בקוד שרשמנו מקודם בחלון הגרפי שלנו (שגם הוא דורש שם נכון ?). אם אתם עובדים בלזרוס גרסה 0.9.28 ומעלה, אז נוכל לבחוrefactoringר את 2 הפרוצדורות וללחוץ על צמד המקשים CTRL+J או ללחוץ על הצלמית של פנקס עם עפרון בצד שמאל. עכשיו עם TAB ניגש לSQLQuery1 ונשנה אותה לtblAuthors ולזרוס ישנה לנו בפעם אחת את כל המקומות שהשם הקודם הופיע לנו. נלחץ על Escape בסיום העריכה.

חשוב להבין איך הrefactoring בלזרוס עובד. יש כמה רמות לrefactor. כאשר אנחנו משנים שם לפקד כל היחידה שבה הפקד נמצא וכן כל הרכיבים המשתמשים בו ידעו על שינוי השם והשם ישתנה בהתאם עבורינו.  במקומות בהם נרשם קוד ידני, לא יהיה שינוי אוטומטי, אבל ישנם כמה דרכים לגרום לשינוי לתפוס. הדרך שהצגתי לfind or rename identifierמעלה, מאפשרת לעשות שינוי ידני. עוד צורה מקובלת היא להשתמש באפשרות Rename Identifier בתפריט ה Refactoring ולבחור בצורת שינוי השם הרצויה לנו.

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

עכשיו נספק שם לטבלה השניה שלנו. לטבלה השנייה שלנו נקרא tblPosts.

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

השלב הבא יהיה כמובן ליצור חלון עבור הטבלה החדשה שיצרנו. ניתן לו את הכותרת 'Post Editor' וניצור את החלון על ידי התפריט File ושם האפשרות New Form. עכשיו נוכל להביא את החלון של frmAuthors לא להיווצר בזמן ריצה בצורה אוטומטית על ידי הטאב של From בחלון של Project Options או בקוד המקור של הפרוייקט שלנו כאשר נמחק את השורה:

Application.CreateForm (TfrmAuthors , frmAuthors);

ונוכל לעבוד על החלון החדש.

הדבר הראשון שנעשה לחלון החדש יהיה לשנות לו את השם ל frmPosts. נוסיף לו את השדות ממש כמו בחלון ה Authors שלנו, ול Excerpt נגדיר בתכונה שהוא לקריאה בלבד. עבור הBody נעבוד עם פקד בשם TNotebook הממוקם בלשונית Additional והוא אחד מתוך 4 צורות עבודה עם לשוניות שיש בלזרוס. נוסיף לו דף על ידי לחיצה ימנית על הפקד ובחרית האפשרות Add Page. נספק לו את השם HTML עבור העריכה שלנו (בעתיד אולי נוסיף לו גם אפשרות צפייה בגוף ולכן בחרנו בלשוניות). בתוך הלשונית (כשהיא נבחרה) נעבור ללשונית Common Controls ונבחר בפקד TToolBar ונשים אותו בדף שיצרנו. הוא יישור לנו בצורה אוטומטית כלפי מעלה.

אנחנו הולכים ליצור סרגל כלים עם כפתורים אשר יבצעו הדגשה, קישור, הוספת חלק של קוד, הוספת רשימות, הוספת תמונה והוספת חלק ל Excerpt (לא באמת נחוץ, אבל בשביל ההדגמה). בשביל ליצור כפתורים, נלחץ על המקש הימני של העכבר כאשר אנחנו עומדים על הסרגל ונבחר באפשרות New Button. בשביל מפריד, נוכל לבחור ב New Separator .

עכשיו בגלל שאנחנו משתמשים במשהו שיהיה רק עבור החלון הזה של Posts, נשים 2 רכיבים לא וויזואליים בחלון. הראשון הוא מטאב ה Standard שנקרא TActionList והשני הוא TImageList מטאב Common Controls.

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

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

לאחר שהוספנו את התמונות נקשר בין TActionList לבין ה TImageList שלנו באמצעות התכונה Images ב TActionList. לאחר מכן, נלחץ לחיצה כפולה על הרכיב ונוסיף פעולה חדשה. נכתוב בתכונה Category את המילה Editing וכך ניצור קבוצה עבור הפעולה הנוכחית. ניתן שם לפעולה acBold, ניתן את מקש הקיצור Ctrl+b ונשתמש בתכונה Tag. התכונה Tag היא תכונה שקיימת בכל רכיב בלזרוס והתפקיד שלה זה מספר שלם שהוא חופשי לשימוש שלנו כמו שאנחנו רוצים. אני אשתמש בו במקרה הזה למפות פעולות שאנחנו מגדירים ב Action. ל Bold ניתן מספר 1. לאחר מכן נכתוב ב Caption את המילה Bold ונכתוב איזשהו טיפ שיסביר במה מדובר.

נוסיף מתחת לpanel שהוספנו את TDBMemo ונקשר אותו לשדה של Body. לאחר מכן בתכונה של Align נגיד לו Client והוא יתפזר על כל הטאב שלנו פרט למיקום שנתפס על ידי ה Toolbar.

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

function TfrmPosts.CanAddAction : Boolean ;
begin
  Result := dbmmoBody.SelLength >= 1;
end;

שמתי את ההגדרה הראשונית בחלק ה public של חלון ה Posts שלנו וכמובן שהשתמשתי ב CTRL+Shift+C בשביל להיכנס לתוך הפונקציה ולכתוב את הקוד. מה שהקוד בודק זה אם נבחר לפחות תו אחד ומחזיר ערך חיובי אם כן ושלילי אם לא.

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

procedure TfrmPosts.AddTag (const BeginTag , EndTag : String );
begin
  if CanAddAction then
    dbmmoBody.SelText := BeginTag + dbmmoBody.SelText + EndTag;
end;

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

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

procedure TfrmPosts.AddOpenCloseTag (const aTag : String);
begin
  AddTag('<' + aTag + '>', '</' + aTag + '>');
end;

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

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

procedure TfrmPosts.acBoldExecute (Sender : TObject);
begin
  case TComponent(Sender).Tag of
    1 : AddOpenCloseTag(txtTagStrong);
  end; // case TComponent(Sender).Tag
end;

הקוד שהוספנו יוצר בעצם השוואת Case אשר בודקת את הערך של Tag. השתמשנו במחלקה TComponent היות והיא המחלקה הנמוכה ביותר שמגדירה את Tag וכל הרכיבים בלזרוס יורשים ממנה. אנחנו אומרים לה להשתמש ב instance ש"הרים" את הפעולה באמצעות Sender וכך אנחנו יוצרים קוד גלובלי.

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

dd

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

  1. פינגבק: מפתחות זרים ללא מפתחות « לראות שונה

כתיבת תגובה

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

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s