ביטול מחיקה בריילס (כמו בג'ימייל)

30/10/2019

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

1. בגדול - איך זה עובד

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

  1. הכי חשוב לא באמת למחוק את ההודעות, אלא רק לעשות כאילו. לכן ניצור עמודה ב DB בשם archived והודעות שנמחקו יסומנו בתור true בעמודה זו.

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

  3. ברגע שמשתמש מוחק הודעה אנחנו נעביר את ההודעה לארכיון ונגריל מזהה אקראי ממש עבור recovery_token. בדף האינדקס החוזר נשלח את המזהה הזה. משתמש יוכל לפנות לנתיב חדש (נקרא לו recover) בצירוף המזהה האקראי וכך לשחזר את ההודעות שבטעות מחק, מה שנקרא Undo.

קוד? יאללה קוד.

2. פעולת המחיקה המרובה ב Controller

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

  def destroy_multiple
    recover_token = SecureRandom.hex(25)
    Message.where(id: params[:message_ids]).update_all(archived: true, recover_token: recover_token)

    flash[:recover_token] = recover_token
    redirect_to messages_path
  end

3. פעולת שחזור לפי Token

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

  def recover
    recover_token = params[:token]
    Message.where(recover_token: recover_token).update_all(archived: false)
    redirect_to messages_path
  end

4. חיבור הפעולות לנתיבים

בקובץ config/routes.rb נרצה לחבר את שתי הפעולות החדשות שלנו ל Controller. את הפרויקט לדוגמא התחלתי עם Scaffold כך שהקובץ יחסית ריק, הנה הוא עם התוספת:

Rails.application.routes.draw do
  resources :messages do
    collection do
      delete 'destroy_multiple'
      post 'recover'
    end
  end
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

5. הצגת הקישור על הדף

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

  <% if undo_token = flash[:recover_token] %>
    Messages were deleted. 
    <%= link_to('undo ?', recover_messages_path(token: undo_token), method: :post) %>
  <% end %>

6. מחשבות לעתיד

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

בעולם עוד יותר יפה היינו שומרים States של המודלים שלנו בטבלא נפרדת ומנהלים את הכל מחוץ למודלים, וכך יכולים לטפל גם ב Undo לשינוי ולא רק למחיקה. זה מה שעושה הג'ם ממנטו.

העליתי אגב לגיטהאב את פרויקט הדוגמא אז אם יש חלקים בקוד שמעניינים אתכם ושכחתי להדביק כאן מוזמנים להעיף מבט בקישור https://github.com/ynonp/undo-demo