ToCode
الذهاب إلى القناة على Telegram
1 419
المشتركون
+124 ساعات
-17 أيام
-530 أيام
أرشيف المشاركات
1 419
השתמשו באיזה ספריות שתרצו
ראיתי את המשפט הזה באיזה משימת בית בריאקט שמישהו פרסם, משהו כמו "כתבו קומפוננטה שמציגה רשימה של נתונים מחולקים לעמודים וכפתורים לדפדף קדימה ואחורה. השתמשו באיזה ספריות שתרצו".
הבעיה עם "איזה ספריות שתרצו" היא שזו הסחת דעת. יש אנשים שמכירים ספריה או שתיים שבדיוק פותרות את הבעיה ויסיימו מהר את המשימה; יש אחרים שמכירים ספריות שלא הכי מתאימות אבל יעשו מאמץ להתאים אותן למשימה וייכשלו או יסתבכו; אחרים יחפשו בגוגל ספריות ויבזבזו את כל זמן המשימה על חיפוש הספריה הטובה ביותר ועוד אנשים יפספסו לגמרי את הרעיון של ספריות ויכתבו הכל בריאקט בלי ספריות הרחבה, מה שיגרום לקוד שלהם להיראות יותר ארוך או שהמשימה תיקח להם יותר זמן.
שתי אפשרויות יותר טובות לדעתי למשימות בית:
1. אין להשתמש בספריות הרחבה - תכתבו את קוד התקשורת לבד בריאקט ואת העיצוב ב CSS או style. ברור שייקח יותר זמן ולא בטוח שנראה את המוצר הכי מעוצב, אבל אולי נרוויח שיחה מעניינת על הקוד.
2. השתמשו רק בספריות X, Y ו Z - פה יש סוג של ייתרון לאנשים שכבר מכירים מראש את הספריות שבחרנו למשימה, אבל רוב הזמן בפרונט אנד קל לאנשים להשתמש בספריות רלוונטיות גם אם לא עבדו איתן בעבר. כלומר מי שעבד עם react-query יצליח להסתדר מהר עם swr. מי שעבד עם emotion יצליח להסתדר עם styled components וכו. ברור אל תבחרו פה רידאקס או ספריות שקשה ללמוד, אלא אם כן אתם ספציפית מחפשים לגייס אנשים שמכירים ספריות אלה.
יותר מדי גמישות יכולה לעבוד לרעתכם, גם במשימות בית.
1 419
היום למדתי: מערכת הקבצים הסודית של הדפדפנים
נמאס לכם מהמגבלה של 5 מגה של local storage? רוצים לכתוב ולקרוא מהר לקבצים שיישמרו אבל אתם תקועים בתוך דפדפן? מסתבר שיש פיתרון יחסית חדש ולא מסובך שנקרא Origin private file system או בקיצור OPFS. מנגנון זה מספק לנו משהו שעובד בדיוק כמו מערכת קבצים אבל סגור בתוך הדפדפן. כרום אצלי על המחשב נתן לי Quota של 500 ג'יגה אפילו בלי לבקש רשות.
דוגמה 1 - כתיבת קובץ
הממשק זמין דרך האוביקט
navigator.storage, ובשביל לראות איך זה עובד הלכתי ל ChatGPT וביקשתי שתי דוגמאות. דוגמה ראשונה כותבת קובץ לדיסק:
async function create1MBFileInOPFS() {
// Request a handle to the OPFS root directory
const rootDir = await navigator.storage.getDirectory();
// Create a new file in OPFS
const fileHandle = await rootDir.getFileHandle('1MB_text_file.txt', { create: true });
// Create a writable stream
const writableStream = await fileHandle.createWritable();
// Generate 1MB of text data (1 character = 1 byte for plain text)
const sizeInBytes = 1024 * 1024; // 1MB
const largeText = 'A'.repeat(sizeInBytes); // A string with 1MB of 'A' characters
// Write the text data to the file
await writableStream.write(largeText);
// Close the writable stream to save the file
await writableStream.close();
console.log('1 MB text file created in OPFS');
}
נקרא את זה יחד -
1. הפקודה getDirectory נותנת לי נקודת כניסה ל storage.
2. הפקודה getFileHandle מחברת אותי לקובץ, והאופציה create אומרת אם ליצור את הקובץ במידה ולא קיים.
3. הפקודה createWritable מחזירה זרם לכתיבה לקובץ.
4. הפקודה write של זרם הכתיבה מקבלת מחרוזת וכותבת אותה לקובץ.
5. בסוף מפעילים close כדי לסיים את העבודה עם הקובץ.
כל הפונקציות הן אסינכרוניות ועובדות גם מ Web Worker.
דוגמה 2 - הצגת רשימת קבצים
הדוגמה השניה מציגה את רשימת הקבצים:
async function listFilesInOPFS() {
// Request a handle to the OPFS root directory
const rootDir = await navigator.storage.getDirectory();
// Iterate over the entries in the root directory
for await (const [name, handle] of rootDir) {
if (handle.kind === 'file') {
console.log(\File: ${name}\);
} else if (handle.kind === 'directory') {
console.log(\Directory: ${name}\);
}
}
}
// Call the function to list files in the root directory
listFilesInOPFS();
שוב משתמשים ב getDirectory כדי להיכנס לתיקייה ואז אפשר לרוץ בלולאה על אותה התיקיה כדי לקבל את הקבצים והתיקיות שבתוכה. כל דבר שהוא מסוג directory מאפשר איטרציה עם for כך שאפשר היה להמשיך רקורסיבית ולהדפיס את כל הקבצים והתיקיות על הדיסק הוירטואלי.
נשים לב-
1. הדיסק הוירטואלי מהיר ומספר המון מקום עבודה. זה טוב אם אתם בונים מערכת ווב וצריכים לשמור הרבה מידע שיהיה זמין אופליין.
2. הדיסק הוירטואלי ספציפי לדומיין, בדיוק כמו עוגיות ו local storage.
3. אחריות שלכם לנקות את הקבצים עם פקודת removeEntry כשאתם כבר לא צריכים אותם.
4. אפשר לראות את הגודל שתופסים כל הקבצים במסך כלי הפיתוח בטאב application. לא מצאתי במסך כלי הפיתוח דרך לשוטט בתיקיות הוירטואליות או בתוכן הקבצים עצמם. כן אפשר לעשות את זה מתוך קוד JavaScript כל עוד אתם בדומיין שיצר את הקבצים.1 419
ה vimrc שלי לפיתוח ריילס
הרומן שלי עם vim הוא מחזורי - פעם בכמה זמן אני מרוקן כמעט לגמרי את ההגדרות, לאט לאט אני מוסיף פלאגינים והגדרות עד שהוא מתנפח כל כך שאני לא מצליח להשתמש בו ואז אני מרוקן שוב כדי לחזור לבייסיקס. השבוע חזרתי לבייסיקס וזה קובץ ה vimrc הכי קטן שבניתי שעדיין מספיק לי בשביל לכתוב ריילס:
call plug#begin()
" List your plugins here
Plug 'tpope/vim-sensible'
Plug 'tpope/vim-rails'
Plug 'ctrlpvim/ctrlp.vim'
Plug 'preservim/nerdtree'
Plug 'nanotech/jellybeans.vim'
call plug#end()
let mapleader = ","
syntax on
filetype on
set number
colo jellybeans
set hidden
set shiftwidth=2
set expandtab
set tabstop=2
set wildmenu
set incsearch
set hlsearch
set ruler
set smartindent
nnoremap <silent> <C-l> :noh<cr>
let g:ctrlp_custom_ignore = '\v[\/]\.(git|hg|svn)$|node_modules\/'
nnoremap <Leader>n :NERDTreeToggle<cr>
הסבר בקצרה:
1. הפלאגין vim-rails הוא קסום ונותן המון קיצורי מקלדת לעבודה עם ריילס. לקח לי שנים להכיר אותו ואני לא מוותר עליו.
2. פלאגין ctrl-p מחפש בקבצים מהר, ו nerdtree פותח עץ תיקיות. כן יש יותר חדשים מהם אבל שניהם עובדים לי טוב. שימו לב שלקראת סוף הקובץ אני אומר ל ctrl-p ממה להתעלם וממפה את ההפעלה המהירה של Nerdtree.
3. ג'ליבינס זו ערכת צבעים מוצלחת.
4. כפתור הלידר הוא פסיק, כי אני רגיל.
5. חיפוש אינקרמנטלי עם צבעים מגיע מ incsearch ו hlsearch והמיפוי של Ctrl L עוזר לנקות את ההדגשה של החיפוש.
אין השלמות אוטומטיות או לינטינג בינתיים אבל בטח אוסיף בהמשך. התקנת פלאגינים מבוצעת עם פלאג אותו התקנתי בנפרד מכאן:
https://github.com/junegunn/vim-plug
יש לכם טיפים והגדרות וים שאתם לא יכולים לחיות בלי? שתפו בתגובות אולי אוכל לאמץ כמה רעיונות.1 419
ואם מחשבון היה טועה?
ישבתי לכתוב קטע קוד היום וראיתי שאני מסתבך אז קראתי לחבר טלפוני, כלומר ל Chat GPT. מהצד שלי זה נראה ככה-
1. הי AI אני צריך קוד שעושה X
2. תודה! אבל הקוד שהדפסת לא נראה יעיל במיוחד. יכול להציע 5 רעיונות אחרים?
3. מספר שלוש נראה טוב זה בגדול הכיוון שרציתי לקחת. בבקשה תתקדם איתו.
4. שמע זה כמעט עבד אבל הראה תוצאה לא נכונה. הנה ה Data עליו הרצתי. רואה את הבעיה?
5. תודה! זה עובד מעולה.
מצד אחד יצאתי עם קוד שנראה עובד. מצד שני ואולי היותר חשוב, הפסדתי את ההזדמנות לפתור את הבעיה בעצמי, להיתקע וללמוד. אבל רגע, מה בעצם ההבדל בין זה לבין מחשבון? אתה לא מציע כאן לבצע חישובים מסובכים על דף רק בשביל ה"חוויה" או ה"מיומנות" של פיתרון תרגילי חילוק ארוך?
ובאמת הפוסט הזה הוא יותר שאלה מתשובה. ה AI בכל מקום, אני יכול להריץ אותו מקומית ולגמרי בחינם, והוא חוסך המון המון זמן. מצד שני ובניגוד למחשבון התוצאה שמקבלים מ AI לא תמיד נכונה, ואפילו כשהיא עובדת לא ברור עד כמה היא טובה. בכתיבת קוד אנחנו עדיין הרבה יותר מודעים לכל סימן מאשר בקריאה והעתקה.
אולי הפיתרון הכי טוב כרגע הוא לדבר עם ה AI ואז ללכת לכתוב את הקוד מאפס ב IDE. ואולי מה שצריך זה להתרגל לדבג הרבה יותר, כי לאורך זמן אותם קודים שרוב הזמן עובדים הולכים להרכיב את רוב המערכות שלנו בעתיד.
1 419
חדש באתר קורס מבוא ל Git
את קורס Git Hero שבאתר הקלטתי אחרי הרבה הדרכות גיט אצל לקוחות. בכל ההדרכות והייעוצים מצאתי אנשים שהתחילו לעבוד עם גיט בלי ללמוד אותו בצורה מסודרת. הם הצליחו להסתדר רוב הזמן אבל כשדברים הסתבכו הם עשו טעויות, בגלל שלא הבינו איך גיט באמת עובד ומה ה Best Practices שכדאי לאמץ. באותו קורס אני מראה לפרטי פרטים איך גיט עובד, כולל איך נשמר המידע בתיקייה
.git שלו - מה שנותן לתלמידים הבנה מעמיקה וביטחון בעבודה עם גיט על פרויקטים גדולים.
מאז שהעליתי אותו גיליתי שיש לא מעט אנשים שרק מתחילים לעבוד עם גיט וגם היו שמחים לקבל הדרכה קצרה על מה זה בכלל גיט, למה צריך אותו ואיך עושים דברים פשוטים. דווקא בגלל כמות המידע ואינסוף המדריכים שיש באינטרנט על גיט, אנשים הלכו לאיבוד והיו רוצים מדריך אחד, ממוקד שיאפשר להתחיל להשתמש ב Git על פרויקט או לשתף פרויקט ברשת דרך GitHub.
היום אני שמח להשיק בדיוק את זה - קורס גיט מקוצר ומדויק, לאנשים שאף פעם לא ראו גיט ורוצים להשתתף בפרויקט עם גיט או להתחיל פרויקט ראשון שלהם. הקורס לא דורש ידע קודם, אנחנו עובדים שם עם אפליקציית Github Desktop כך שאפילו לא צריך להכיר את כלי שורת הפקודה, ובפחות משעתיים של לימוד ייתן לכם את כל הכלים הבסיסיים כדי להתחיל לעבוד עם גיט בלי לטעות ובלי להיבהל.
קורס מבוא ל Git לא מחליף את קורס Git Hero שבאתר אלא משלים אותו בתור צעד ראשון. אם חיפשתם דרך מהירה וקלה להתחיל לעבוד עם Git אני מזמין אתכם ואתכן להעיף מבט:
https://www.tocode.co.il/bundles/pregit
בניגוד לשאר הקורסים באתר הקורס הזה חינמי לגמרי גם ללא מנוי. תרגישו חופשי להיכנס וללמוד או להעביר אותו לחברים וחברות שצריכים התחלה מזורזת ב Git.1 419
משחקים קצרים וארוכים
למשחקים קצרים יש סוף וכדאי לנצח בהם. משחקים ארוכים פשוט ממשיכים וקובעים איך נגיע למשחק הקצר הבא.
אז ראיון עבודה או אפילו חיפוש עבודה זה סוג של משחק קצר, שנגמר כשמוצאים עבודה. למידה היא סוג של משחק ארוך שקובע איך נגיע לראיון העבודה הבא.
העלאת גירסה היא משחק קצר שייגמר כשהגירסה החדשה תהיה באוויר. תהליך פיתוח נכון הוא המשחק הארוך והוא יקבע כמה מבוהלים נהיה בהעלאת הגירסה הבאה.
בעבודה על בעיות מעניינות החוכמה היא לשים לב למשחקים הקצרים והארוכים שמעורבים ולנהל את המשאבים שלנו כדי שנוכל להצליח בשני הסוגים.
1 419
ניסוי ריילס: משחק איקס עיגול חלק 2
בחלק הקודם של הניסוי בנינו משחק איקס עיגול בלי לכתוב JavaScript שעדיין עבד די מהר. בחלק הזה נראה איך להוסיף תמיכה ב Web Sockets כדי שאפשר יהיה לשחק מכמה דפדפנים.
איך זה עובד
הטריק של ריילס כדי לוותר על JavaScript נקרא Turbo, ובחלק הקודם ראינו איך באופן אוטומטי ריילס מוסיף קוד לטפסים כדי שהם יוגשו ב Ajax. בצד שרת ראינו את השורה:
format.turbo_stream { render turbo_stream: turbo_stream.replace(@game) }
שגורמת לשרת להחזיר את ה HTML של המשחק, ושוב באופן אוטומטי קוד ה Ajax בדפדפן לוקח את הטקסט שהוחזר ומחליף בזה את תוכן ה div של המשחק.
בשביל להוסיף תמיכה ב Web Sockets נשתמש עדיין בטורבו אבל בצורה קצת אחרת:
1. נפתח ערוץ תקשורת Web Socket מהדפדפן לשרת.
2. בשרת כל פעם שיש שינוי במשחק נשלח הודעה לערוץ.
3. בדפדפן נקשיב להודעות בערוץ ונתייחס אליהן כמו לתשובות של טופס, כלומר נחליף את תוכן ה div של המשחק במידע שהגיע בערוץ.
קוד
בתור התחלה אני מעדכן את הקוד ב games_controller.rb לקוד הזה:
def play
GameMove.create!(game_move_params)
@game.broadcast_replace_to(
"game_#{@game.id}",
partial: "games/game",
locals: { game: @game }
)
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(@game) }
format.html { redirect_to show_game_url(@game.id) }
end
end
השורה החדשה היא השורה השניה בפונקציה. היא שולחת הודעת "החלפה" לערוץ ששמו מורכב מהמילה game, קו תחתי ואז מזהה המשחק. בשביל תוכן ההודעה היא מפעילה את התבנית games/_game.html.erb ומעבירה לה בתור משתנה את המשחק הנוכחי. אגב גם את התבנית קצת שיניתי כדי שתעבוד עם שם המשתנה game ולא @game. אבל השינוי הכי גדול בקובץ הוא השורה הראשונה שמקשיבה לעדכונים מהערוץ ומטפלת בהם:
<%= turbo_stream_from dom_id(@game) %>
<div id="<%= dom_id game %>">
<p>Now playing: <%= game.next_player %></p>
<table>
<tbody>
<% (0..2).each do |row_index| %>
<tr>
<% (0..2).each do |column_index| %>
<% text = game.at(row_index, column_index) %>
<td
class="<%= class_names(playable: text == '.') %>">
<%= form_for :game_move, url: play_game_path(game.id) do |f| %>
<%= f.hidden_field :row, {value: row_index} %>
<%= f.hidden_field :column, {value: column_index} %>
<%= f.hidden_field :game_id, {value: game.id} %>
<%= f.hidden_field :player, {value: game.next_player } %>
<%= f.submit text %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>
וכן זה כל מה שהיה צריך.
מה קיבלנו
התוצאה די מרשימה:
1. בגלל שהעדכון היה בתבנית המשחק, כולל תוספת ההאזנה ל Web Socket, אז כל פעם שתבנית זו תופיע ריילס יעדכן אותה בצורה דינמית דרך ה Web Socket מצד השרת. זה פותר לנו את כל בעיות ניהול הסטייט בכלום קוד - כל הסטייט מאוחסן ב DB, כל שינוי ב DB נשלח אוטומטית לדפדפן דרך ה Web Socket ולכן הדפדפן תמיד יראה את מצב העולם הכי מעודכן.
2. בכל מקום שמשתמשים בתבנית המשחק היא תתעדכן בצורה אוטומטית מהשרת. זה נכון במסך המשחק אבל גם במסך רשימת המשחקים. אפשר לראות בכלי הפיתוח שבמסך רשימת המשחקים יש עדיין Web Socket אחד אבל הוא מעביר הודעות למספר ערוצים, ערוץ לכל משחק פעיל.
עכשיו נכון שיבואו חכמים ויגידו שזה לא מספיק טוב כי אי אפשר לשמור את כל הסטייט בשרת, ושכששומרים סטייט צד-לקוח בריאקט בדפדפן אנחנו יכולים לשפר משמעותית את הביצועים ושמשתמשים לפעמים נמצאים אופליין ובכל מקרה לא רוצים לחכות לתשובה מהרשת בשביל לראות עדכונים. הכל נכון, אבל חשוב לסייג ולהבין איזה מערכת אנחנו בונים. בסוף יש פה Trade Off מחשבתי ו Trade Off של מורכבות. אם אני לא מתעסק בכלל עם סטייט צד לקוח וסינכרון שלו עם השרת אני יכול לוותר על הרבה כאב ראש בזמן הפיתוח ולקבל קצב פיתוח מהיר בהרבה.
נ.ב. קוד הפרויקט המעודכן נמצא בקישור:
https://github.com/ynonp/rails-demo-tic-tac-toe1 419
2. הטופס מציג את תוכן התא ושדות נסתרים שמחזיקים את המיקום של התא, כדי שכשהמידע הזה יגיע לשרת אפשר יהיה ליצור ממנו GameMove.
3. כפתור Submit של הטופס פונה לנתיב מיוחד שבניתי בשרת לטפל במהלכים במשחק.
טיפול בהגשת הטופס
החלק האחרון של הניסוי מוגדר בקובץ
app/controllers/games_controller.rb ובדיעבד אני חושב שעדיף היה לשים אותו ב game_moves_controller אבל כמו שכתבתי בתחילת הפוסט אנחנו באילוצי זמן היום. זה הקוד שהוספתי לשם:
def play
GameMove.create!(game_move_params)
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(@game) }
format.html { redirect_to show_game_url(@game.id) }
end
end
def game_move_params
params.expect(game_move: [:row, :column, :player, :game_id])
end
סך הכל לא נורא - יש פה פונקציה אחת שמוציאה מהטופס את הפרמטרים שמעניינים אותי, ופונקציה נוספת שמטפלת ביצירת ה GameMove. נכון אין טיפול בשגיאות או אבטחת מידע. זה ניסוי אל תיקחו מפה קוד רק רעיונות. בכל מקרה השורה שעושה את הקסם היא:
format.turbo_stream { render turbo_stream: turbo_stream.replace(@game) }
שורה זאת גורמת לשרת להחזיר לטופס את התוכן של התבנית שראינו קודם, כלומר השרת הוסיף GameMove חדש לבסיס הנתונים, הריץ את קוד התבנית מחדש ושלח את ה HTML שיצא לדפדפן. באופן אוטומטי JavaScript של טורבו שרץ בדפדפן לוקח את ה HTML הזה ומחליף את האלמנטים על המסך בקוד החדש, מה שגורם למסך להתעדכן ולהציג את מצב המשחק אחרי המהלך החדש.
מסקנות ותוכנית להמשך
אם הייתי צריך לכתוב איקס עיגול בריאקט בצד לקוח בלבד אני בטוח שהייתי מסיים יותר מהר, אפילו אם הייתי צריך להשתמש ב Redux (וכנראה שלא הייתי בוחר ברידאקס לניסוי כזה). אבל אז לא היה לי חיבור לבסיס נתונים. מהבחינה הזאת קוד ריילס הרבה יותר דומה לפיתוח עם next ו Server Actions, ולדעתי בהשוואה כזאת הקוד של ריילס יוצא יותר נקי כי ל next עדיין אין מנגנון אוטומטי נוח לעדכון המסך אחרי הגשת הטופס. וכן ברור לי שהדוגמה פה מומצאת וקלה וזה בסך הכל משחק איקס עיגול ובאפליקציה גדולה חייבים JavaScript אבל אולי יש גם מספיק אפליקציות לא גדולות שאפשר לכתוב בצורה כזאת.
החלק השני שאני מתכנן לניסוי יהיה חיבור Web Sockets. כרגע המשתמש שהגיש את הטופס רואה את העדכון של לוח המשחק על המסך, אבל משתמשים מחלונות אחרים יצטרכו לרענן בצורה יזומה את החלון כדי לקבל את גירסת המשחק המעודכנת ביותר. בעזרת Web Sockets נוכל להודיע לכל מי שמסתכל על משחק מסוים על עדכונים וכך מספר משתמשים יוכלו לשחק בכמה חלונות.
קוד הניסוי המלא נמצא בגיטהאב בקישור:
https://github.com/ynonp/rails-demo-tic-tac-toe1 419
ניסוי ריילס: משחק איקס עיגול
אני כותב פה הרבה על פיתוח אפליקציות JavaScript עשירות ב React ועוד פריימוורקים וחשבתי שיהיה מעניין לעשות ניסוי של כתיבת יישום אינטרקטיבי בפריימוורק צד-שרת לשם שינוי. וכן ברור שריילס מאוד טוב בעבודה עם בסיסי נתונים ומשימות ברקע וכל מה ששרתים עושים, אבל האם הוא יכול לעזור גם בפיתוח יישומים "מודרניים"? אנסה בניסוי הזה לענות על השאלה.
עקב אילוצי זמנים אני מחלק את הניסוי ל-2. בפוסט היום אני אראה קוד למשחק איקס עיגול בריילס שעובד בצורה אינטרקטיבית ובלי כתיבת JavaScript כלל. המשחק עובד כשמשחקים בו מאותו דפדפן ממש בסדר. בחלק השני של הניסוי אוסיף לזה גם Web sockets בשביל לבנות משחק איקס עיגול שאפשר לשחק מכמה מחשבים במקביל.
הכלים שריילס מספק
כלי העבודה המרכזי לניסוי נקרא Turbo Stream. זה מנגנון יחסית חדש של ריילס שעוזר לכתוב יישומי ווב מודרניים ומהירים אבל בלי לכתוב JavaScript. הרעיון מבוסס על Ajax אוטומטי כך שאנחנו כותבים HTML עם טופס וטורבו באופן אוטומטי מגיש את הטופס בצורה אסינכרונית, לוקח את תשובת השרת ומדביק אותה בתוך ה DOM במקום מה שהיה שם קודם וככה רואים על המסך באופן מיידי את התוצאה. הייתרון מבחינת קוד הוא מאוד מעניין כי פיתוח כזה נותן לנו חיבור אוטומטי לבסיס נתונים ולכל מה שצריך בצד שרת.
אני מתחיל את משחק האיקס עיגול שלי בשתי טבלאות בבסיס הנתונים - טבלת משחקים וטבלת מהלכים במשחק:
ActiveRecord::Schema[8.0].define(version: 2024_10_09_113739) do
create_table "game_moves", force: :cascade do |t|
t.integer "game_id", null: false
t.integer "turn"
t.string "player"
t.integer "row"
t.integer "column"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["game_id"], name: "index_game_moves_on_game_id"
end
create_table "games", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_foreign_key "game_moves", "games"
end
נתתי לריילס ליצור קונטרולרים ומודלים לשתי הטבלאות עם rails g scaffold ויצאתי לדרך.
פיתוח מתודות עזר במודל
בשביל שיהיה קל להציג את הלוח הוספתי למודל את הפונקציות הבאות:
class Game < ApplicationRecord
has_many :game_moves, inverse_of: :game
def at(row, column)
start = [['.', '.', '.'],
['.', '.', '.'],
['.', '.', '.']]
board = game_moves.reduce(start) do |g, move|
g.tap { |g| g[move.row][move.column] = move.player }
end
board[row][column]
end
def next_player
['X', 'O'][game_moves.count % 2]
end
end
הפונקציה next_player מחזירה מי השחקן שעכשיו צריך לשחק ו at מחזירה מה מצויר במשבצת מסוימת. נשים לב שהנחת העבודה כאן היא שכל המהלכים חוקיים ושאין חשיבות לסדר בין המהלכים, וגם לא טיפלתי במשחקים שהסתיימו וניצחונות.
הצגת לוח המשחק
עיקר הקוד של הניסוי הוא בקובץ app/views/games/_game.html.erb. זה תוכן הקובץ:
<div id="<%= dom_id game %>">
<p>Now playing: <%= @game.next_player %></p>
<table>
<tbody>
<% (0..2).each do |row_index| %>
<tr>
<% (0..2).each do |column_index| %>
<% text = @game.at(row_index, column_index) %>
<td
class="<%= class_names(playable: text == '.') %>">
<%= form_for :game_move, url: play_game_path(@game.id) do |f| %>
<%= f.hidden_field :row, {value: row_index} %>
<%= f.hidden_field :column, {value: column_index} %>
<%= f.hidden_field :game_id, {value: @game.id} %>
<%= f.hidden_field :player, {value: @game.next_player } %>
<%= f.submit text %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
</div>
הקוד הוא Template מסוג ERB שמציירת לוח משחק. אנשי ריאקט יכולים לחשוב על זה כ JSX של Server Component, כלומר הקוד רץ בשרת, מייצר HTML ושולח לדפדפן את ה HTML שיצא. לתבנית יש גישה מלאה לבסיס הנתונים והוא בסך הכל קורא שוב ושוב לפונקציה at כדי לחשב ולצייר את הלוח. החלק המעניין בקוד זה הטופס:
1. לכל תא בלוח המשחק יש טופס משלו.1 419
פייתון בלי GIL
גירסה 3.13 של פייתון ששוחררה ממש עכשיו היא הראשונה שתומכת רשמית בריצה ללא GIL, ואלה חדשות מספיק מלהיבות בשביל לעצור הכל ולראות את האפקט. ברמת התיאוריה ה GIL זה החלק בפייתון ששומר על ה Interpreter כשאנחנו מריצים תוכנית מרובת תהליכונים. גירסה ללא GIL אומרת שאין יותר מבני נתונים בתוך פייתון שצריך להגן עליהם, ולכן פייתון יכולה להריץ קוד מרובה תהליכים בלי נעילות - כלומר הרבה יותר מהר.
בשביל להתקין את גירסת הפייתון נטולת ה GIL עם pyenv הפעלתי:
CONFIGURE_OPTS=--disable-gil PYENV_VERSION_SUFFIX='-free-threaded' pyenv install -f -v 3.13.0rc3t
וידאתי שאני מריץ את הגירסה הנכונה עם:
$ python --version
Python 3.13.0rc3
ואז נתתי לו לספור כמה מספרים ראשוניים יש עד מיליון עם הקוד הזה:
import sys
import math
import multiprocessing.dummy as mp
def is_prime(n):
for i in range(2, int(math.sqrt(n) + 1)):
if n % i == 0:
return False
return True
if __name__ == "__main__":
print(f"GIL enabled = {sys._is_gil_enabled()}")
with mp.Pool(4) as p:
print(sum(p.map(is_prime, range(1_000_000))))
אלה הזמנים שמדדתי בהרצה על הלפטופ:
ynonp@Ynons-MacBook-Air ~/tmp $ time PYTHON_GIL=0 python gil.py
GIL enabled = False
78500
PYTHON_GIL=0 python gil.py 2.83s user 0.04s system 306% cpu 0.938 total
ynonp@Ynons-MacBook-Air ~/tmp $ time PYTHON_GIL=0 python gil.py
GIL enabled = False
78500
PYTHON_GIL=0 python gil.py 2.85s user 0.04s system 305% cpu 0.944 total
ynonp@Ynons-MacBook-Air ~/tmp $ time PYTHON_GIL=0 python gil.py
GIL enabled = False
78500
PYTHON_GIL=0 python gil.py 2.88s user 0.04s system 317% cpu 0.919 total
ynonp@Ynons-MacBook-Air ~/tmp $ time PYTHON_GIL=1 python gil.py
GIL enabled = True
78500
PYTHON_GIL=1 python gil.py 2.61s user 0.05s system 96% cpu 2.753 total
ynonp@Ynons-MacBook-Air ~/tmp $ time PYTHON_GIL=1 python gil.py
GIL enabled = True
78500
PYTHON_GIL=1 python gil.py 2.62s user 0.05s system 97% cpu 2.741 total
ynonp@Ynons-MacBook-Air ~/tmp $ time PYTHON_GIL=1 python gil.py
GIL enabled = True
78500
PYTHON_GIL=1 python gil.py 2.64s user 0.05s system 93% cpu 2.865 total
בממוצע של 3 ריצות עם GIL ו-3 ללא GIL ההבדל הוא בין בערך 2.7 שניות לקצת פחות משניה, כלומר משהו כמו פי 3 יותר מהר לטובת הגירסה ללא GIL. עדיין קשה לדעת איך זה ישפיע על מודולים חיצוניים וקוד קיים ובינינו לא הייתי רץ לשדרג סקריפטים קיימים לגירסה ללא GIL, אבל זה בהחלט משהו שהייתי שוקל לפרויקטים חדשים או לסקריפטים שספציפית משתמשים בקוד מרובה תהליכונים וסובלים מבעיית איטיות.1 419
הבחירה לא לדעת
לא חייבים לדעת ספרדית בשביל להנות ממוזיקה לטינית. לא חייבים להיות רואה חשבון או אפילו להבין במסים בשביל לפתוח עסק. לא חייבים לדעת להתקין שרתים כדי להיות מפתחי ווב ולא צריך להיות רקדנים כדי לרקוד עם המשפחה בחתונה. יש המון דברים שאנחנו עושים בלי להיות מקצועיים בהם או נותנים לאחרים לעשות בשבילנו.
הטריק עם הבחירה לא לדעת הוא לא להפוך את זה לאידאולוגיה. במקום להגיד "עדיף שמפתחים יתרכזו בבניית אלגוריתמים ויתנו לאנשי DevOps להתקין את השרתים. כשמפתחים מתקינים שרתים הם רק יוצרים בעיות אבטחה" אפשר להגיד "אני כרגע מעדיפה להתמקד באלגוריתמיקה באמת יהיה מעניין יום אחד ללמוד יותר על התקנת שרתים".
גם הסיפור שאנחנו מספרים לעצמנו יכול להיות בעתיד חסם לצמיחה. עדיף להשאיר את הדלת הזאת פתוחה.
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
