היום למדתי: הפונקציה gem ב ruby

17/09/2019

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

1. בואו נתקין שתי גירסאות של אותו ג'ם

בשביל הדוגמא נתקין שתי גירסאות של ריילס באמצעות הפקודה gem:

$ gem install rails-6.0.0
$ gem install rails-5.2.3

שתי הגירסאות מותקנות לי עכשיו על המכונה ואני יכול לראות את זה באמצעות הפקודה gem info באופן הבא:

$ gem info rails

*** LOCAL GEMS ***

rails (6.0.0, 5.2.3)
    Author: David Heinemeier Hansson
    Homepage: https://rubyonrails.org
    License: MIT
    Installed at (6.0.0): /Users/ynonperek/.rvm/gems/ruby-2.6.3
                 (5.2.3): /Users/ynonperek/.rvm/gems/ruby-2.6.3

    Full-stack web application framework.

אנחנו רואים ב info שאני משתמש ב rvm וששתי הג'מים מותקנות באותה תיקיית רובי. נסתכל בתיקיה כדי לראות את שתי הגירסאות:

$ $ ls -1 -d /Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/*rails*
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/coffee-rails-4.2.2
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-5.2.3
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-6.0.0
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-controller-testing-1.0.4
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-dom-testing-2.0.3
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-html-sanitizer-1.0.4
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/rails-html-sanitizer-1.2.0
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/sass-rails-5.0.7
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/sass-rails-5.1.0
/Users/ynonperek/.rvm/gems/ruby-2.6.3/gems/sprockets-rails-3.2.1

2. איך טוענים את הגירסא החדשה ביותר

באופן רגיל הפקודה require תטען את הגירסא החדשה ביותר של הג'ם שהיא מוצאת:

> require 'rails'
 => true 

> Rails.version
 => "6.0.0"

3. איך טוענים גירסא ישנה יותר

עם הפקודה gem אני יכול לטעון גירסאות ישנות יותר של הג'ם:

> gem 'rails', '5.2.3'
 => true 

> require 'rails'
 => true 

> Rails.version
 => "5.2.3"

4. גם ריילס משתמש בטריק הזה

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

version = ">= 0.a"

str = ARGV.first
if str
  str = str.b[/\A_(.*)_\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    ARGV.shift
  end
end

gem "railties", version

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

$ rails _5.2.3_ --version
Rails 5.2.3

$ rails _6.0.0_ --version
Rails 6.0.0

5. באנדלר

כל מה שנכתב כאן לא בא להחליף את Bundler. באנדלר מעולה ואני ממליץ לעבוד איתו. מאחר וריילס עובד כברירת מחדל עם באנדלר, ומאחר שבאנדלר הוא חלק מרובי (החל מגירסא 2.6) סיכוי טוב שכל פרויקט רובי חדש שתגיעו אליו כבר משתמש ב Bundler.

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