• בלוג
  • העברת נתיבים מ Rails ל JavaScript

העברת נתיבים מ Rails ל JavaScript

13/04/2019

בוובינר השבוע הראיתי איך אפשר להעביר משתנים ומידע יחסית בקלות בין קוד צד שרת לקוד צד לקוח - באמצעות יצירת div בתוך ה HTML שמכיל את המידע בפורמט JSON. חברים שהשתתפות הזכירו את הג'ם gon שמטפל בכל העסק הזה בצורה אוטומטית.

אתגר יותר מעניין בשילוב הזה בין Rails לאפליקציות צד-לקוח הוא העברת הנתיבים. בעבודה עם ריילס אנחנו רגילים להשתמש ב Route Helpers כדי ליצור באופן אוטומטי נתיבים בתוך קבצי ה .html.erb של היישום. כך לדוגמא התג הבא מייצר את הקישור למסך הלוגין:

<%= link_to 'Login', new_user_session_path %>

וזה מייצר את הקישור למסך צפיה בהודעה לפי ID שלה:

<%= link_to 'Post #4', post_path(id: 4) %>

במעבר לאפליקציות צד-לקוח יש לנו בעיה. אנחנו לא רוצים לכתוב Hard Coded את הנתיבים עצמם. הם כתובים ב config/routes.rb פעם אחת וזה ממש נוח שאפשר לשנות אותם וכל האפליקציה מתעדכנת בצורה אוטומטית. הבעיה שה Route Helpers כגון post_path הם פונקציות, ועוד פונקציות שמיוצרות אוטומטית על ידי ריילס. איך מעבירים את הפונקציות האלה ל JavaScript?

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

נקודת הכניסה תהיה פונקציה ב ApplicationController שתקרא לפני כל פעולה, נקרא לה init. היא תיצור מילון בו המפתח הוא שם של נתיב והערך הוא הנתיב בפורמט של מחרוזת, כאשר הפרמטרים מיוצגים על ידי נקודותיים (כמו שהיינו כותבים אותם בקובץ config/routes.rb). דוגמא למבנה נתונים כזה:

{
    'new_user_session_path': '/users/sign_in',
    'post_path': '/posts/:id',
}

בשביל לייצר את מבנה הנתונים נשתמש ב Rails.application.routes.named_routes. זה המילון בו ריילס מאחסן את כל המידע על הנתיבים שהוא מכיר. אנחנו ניקח את הנתיבים משם, לכל נתיב נמצא איזה פרמטרים הוא מצפה לקבל, ואז נפעיל את פונקציית הנתיב עם הפרמטרים המתאימים כדי לקבל את המילון השלם. צריך לזכור לסנן נתיבים שמתחילים ב rails כי אלה בדרך כלל נתיבים פנימיים של הפריימוורק שאנחנו לא צריכים ונקבל את הקוד הבא:

class ApplicationController < ActionController::Base
  before_action :init

  def init
    @state = {}
    @routes_for_js = Rails.application.routes.named_routes.map do |route_name|
      route = Rails.application.routes.named_routes[route_name]
      route_params = route.parts.reject {|part| part == :format }
      route_params_hash = route_params.map {|x| [x, ":#{x}"] }.to_h
      route_method = route_name.to_s + '_path'
      [
          route_method,
          self.method(route_method).call(**route_params_hash),
      ]
    end.to_h.reject {|k, v| k.starts_with?('rails_')}
  end
end

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

const routes = JSON.parse(document.querySelector('#routes').dataset.routes);
const routeHelpers = {};

for (let [routeName, routeString] of Object.entries(routes)) {
  const routeNameJS = routeName.replace(/_([a-zA-Z]+)/g, word => (
    word[1].toUpperCase() + word.slice(2)
  ));

  routeHelpers[routeNameJS] = routeParams => (
    routeString.replace(/:(\w+)/g, x => routeParams[x.slice(1)])
  )
}

export default routeHelpers;

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

<a href={routeHelpers.newUserSessionPath()}>Login</a>
<a href={routeHelpers.postPath({ id: 4 })}>Watch Post #4</a>