• בלוג
  • היום למדתי: strict loading ב Rails

היום למדתי: strict loading ב Rails

19/07/2026

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

1. הבעיה עם טעינה עצלה

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

Book.all

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

Book.all.first

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

Book.all.each do |book|
    puts book.author.name
end

כיף, נוח, קריא ומאוד לא יעיל. הלולאה תריץ עבור כל ספר שאילתה לבסיס הנתונים כדי להבין את פרטי המחבר ותדפיס את שם המחבר. אם יש לי 10 ספרים זה אומר 11 שאילתות ובגלל זה לולאות כאלה נקראות N+1 Queries.

שתי הדרכים המרכזיות לא לכתוב לולאות כאלה הן טעינה מראש או JOIN. עם טעינה מראש היינו כותבים:

Book.includes(:author).each do |book|
  puts book.author.name
end

ואז ריילס היה מריץ שתי שאילתות, אחת מושכת את כל נתוני הספרים והשניה מושכת את כל נתוני המחברים במכה אחת.

דרך שניה היא JOIN והיא נראית כך:

Book.joins(:author).pluck("authors.name").each do |author_name|
  puts author_name
end

ופה יש לנו כבר שאילתה אחת שמחזירה רק את שמות המחברים.

2. איך לזהות את התבנית השבורה

הדרך הכי קלה לזהות את התבנית השבורה היא בזמן ריצה להסתכל על השאילתות. יש gem בריילס בשם bullet שעושה את זה ואפשר לקרוא עליו כאן:

https://github.com/flyerhzm/bullet

בעבודה עם AI הסוכן הציע להגדיר strict loading כדי לצמצם את הסכנה בתבנית זו:

Mitigation: the `Current`-scoped `localized_translation` association everywhere + `strict_loading` on `phrase_translations` / `token_translations` in development so a forgotten preload raises instead of silently N+1-ing.

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

https://thoughtbot.com/blog/strict-loading-in-rails-8-a-railsy-way-to-avoid-n-1-queries

המשכתי לקצת Issues בגיטהאב ולשיחה עם ChatGPT.

למדתי ש strict loading בגרסתו הראשונית נועד למנוע טעינה עצלה על יחסים מסוימים בריילס. לדוגמה ניתן להגדיר:

class Movie < ApplicationRecord
  has_many :reviews, strict_loading: true
end

ואז הקוד הזה יזרוק שגיאה במצב פיתוח:

puts Movie.first.reviews.count

והדרכים להגיע לביקורות יהיו להשתמש ב includes או join:

movie = Movie.first.includes(:reviews)
puts movie.reviews.count

הרעיון הזה באמת מונע את כל שגיאות ה N+1 אבל הוא מונע גם הרבה שימושים ממש תקינים במנגנון הטעינה העצלה של ריילס ולכן לא נרצה להשתמש בו.

גרסה 7 של ריילס הוסיפה למנגנון מצב פעולה שנקרא n_plus_one_only שאמור לעזור לנו לזהות תבניות N+1 בלי לשבור המון קוד שאנחנו אוהבים. הבעיה שמנגנון זה לא הצליח לזהות בצורה אמינה את כל התבניות הבעייתיות וכאן יש דוגמה למקרה קלאסי שלא נתפס ברשת:

https://github.com/rails/rails/issues/42576

בסוף אין ספק שטעינה עצלה מגיעה עם אתגר אמיתי בזיהוי N+1. אבל האתגר הזה היום הרבה פחות חמור ממה שהיה בעבר בגלל שסוכני קידוד ממש טובים בזיהוי תבניות ו N+1 כמעט תמיד מזוהה כתבנית בקוד המקור. מספיק להוסיף ל AGENTS.md ולפרומפט של ה Code Review שלנו הוראות מפורשות לזיהוי ותיקון שגיאות N+1 כדי לצמצם אותן משמעותית.