[ריילס] היום למדתי: לא מעבירים מזהים למודל ביצירה או עדכון
את הטיפ הבא מצאתי במאמר הזה של חברת Betterment ומיד התחברתי אז אני משתף גם פה.
נתחיל עם קוד ריילס הבא עבור controller:
class Documents::AttachmentsController < ApplicationController
def create
AttachmentLink.new(create_params.merge(document: document)).save!
end
private
def create_params
params.permit(:attachment_id, :caption)
end
def document
current_user.documents.find(params[:document_id])
end
end
הקוד מאפשר להצמיד Attachments למסמך דרך הפונקציה create. הפונקציה מקבלת מהדפדפן מזהה של "קובץ מצורף" ומזהה של "מסמך" ויוצרת AttachmentLink שזה אוביקט חיבור בין השניים.
קחו רגע לקרוא את הקוד ונסו לחשוב מה שבור בו.
1. הבעיה בקוד: מאיפה מגיע Attachment
ראיתם את זה? הפונקציה הפרטית document
טוענת את ה Document מתוך כל המסמכים של המשתמש, וכך מוודאת שאנחנו מנסים להצמיד קובץ מצורף למסמך שלנו.
לעומתה מזהה הקובץ המצורף מועבר בתור id פנימה למודל. טעינת אוביקט ה Attachment מתבצעת אוטומטית בתוך הפונקציה new ולכן תוקף יכול להעביר כל מזהה שירצה וכך לצרף כל Attachment למסמך שלו.
בעצם האחריות המרכזית של ה Controller בריילס היא לוודא שכל המודלים שייטענו מבסיס הנתונים הם מודלים שלמשתמש הנוכחי יש גישה אליהם, ולכן זה הקונטרולר שחייב לטעון את כל המודלים ולוודא הרשאות. אם נעביר מזהים פנימה לפונקציה new אז היא עלולה לגשת למודלים שלא קשורים למשתמש הנוכחי.
ברגע שמבינים את זה התיקון וגם המניעה פשוטים מאוד. ככה זה נראה בקוד:
class Documents::AttachmentsController < ApplicationController
def create
AttachmentLink.new(attach_params).save!
end
private
def create_params
params.permit(:attachment_id, :caption)
end
def attach_params
{
document: document,
attachment: attachment,
caption: create_params[:caption]
}
end
def attachment
current_user.attachments.find(create_params[:attachment_id])
end
def document
current_user.documents.find(params[:document_id])
end
end
החברים ב Betterment גם יצרו כללי Rubocop שיעזרו לכם לוודא שאתם לא מעבירים מזהים של מודלים לתוך פונקציות העדכון והיצירה של ריילס. אפשר למצוא את הכללים בקישור הזה ואם אתם כותבים בריילס שווה לשלב אותם גם ביישומים שלכם: https://github.com/betterment/betterlint/