Гемы, Bundler и структура проекта

Никто не пишет всё с нуля. Сила Ruby — в огромной экосистеме готовых библиотек, которые называются гемами, и в инструменте Bundler, который управляет ими в проекте.
Суть: гем (gem) — это упакованная Ruby-библиотека; RubyGems — её хранилище и установщик; Bundler по файлу Gemfile ставит нужные версии и фиксирует их в Gemfile.lock для воспроизводимости.

Гем — это переиспользуемый пакет кода: веб-фреймворк (rails), HTTP-клиент (faraday), тестовый фреймворк (rspec) и тысячи других. Установить гем глобально можно одной командой, но в реальных проектах так не делают — версии разных проектов начнут конфликтовать. Вместо этого используют Bundler.

# установить гем глобально (для экспериментов)
gem install rails
gem list                 # что установлено
ruby -e "require 'json'; puts JSON.generate({a: 1})"

Разбор: Gemfile и Bundler

В корне проекта живёт Gemfile — декларация «какие гемы и каких версий нужны». Команда bundle install читает его, разрешает совместимые версии и записывает точный их набор в Gemfile.lock. Этот lock-файл коммитят в репозиторий: благодаря ему на любой машине установится идентичный набор версий.

# Gemfile
source "https://rubygems.org"

gem "rails", "~> 8.0"        # любая 8.x, но не 9
gem "pg"                     # драйвер PostgreSQL

group :development, :test do
  gem "rspec-rails"          # только для разработки и тестов
  gem "rubocop", require: false
end
bundle install               # поставить всё из Gemfile
bundle exec rspec            # запустить с версиями из lock-файла
bundle update rails          # обновить конкретный гем

Как работает под капотом

Без Bundler у разработчиков получались бы разные версии гемов — классический баг «у меня работает». Bundler решает «головоломку версий»: он находит набор версий, который удовлетворяет всем ограничениям из Gemfile сразу, и замораживает его в Gemfile.lock. Префикс bundle exec гарантирует, что команда запустится именно с этими версиями, а не со случайными глобальными.

   Gemfile (что хочу)
   "rails ~> 8.0", "pg", "rspec"
        |
        v
   bundle install
        |
   [ Bundler разрешает версии ] -- ищет совместимый набор
        |
        v
   Gemfile.lock (что точно стоит)
   rails 8.0.1, pg 1.5.9, rspec 3.13.0 ...
        |
        v
   коммитим lock --> у ВСЕХ одинаковые версии

Та же идея «зафиксированные зависимости проекта» в экосистеме Python — это requirements.txt / lock-файлы:

# Та же логика на Python ▶
# requirements.txt — аналог Gemfile.lock
deps = {
    "django": "==5.0.1",      # точная версия
    "psycopg": ">=3.1,<4",    # диапазон, как ~> в Ruby
}
for name, version in deps.items():
    print(f"{name}{version}")

Частые ошибки

  • Не коммитить Gemfile.lock. Без него теряется воспроизводимость — у коллег встанут другие версии.
  • Забывать bundle exec. Запуск утилиты напрямую может подхватить не ту версию гема, минуя lock-файл.
  • Ставить гемы глобально для проекта. Это путь к конфликтам версий между проектами. Всё проектное — через Bundler.

Best practices

  • Всегда коммитьте Gemfile.lock в приложениях — это гарантия одинаковой среды у всей команды.
  • Указывайте версии разумно: ~> (pessimistic, «не выше следующей минорной») балансирует свежесть и стабильность.
  • Группируйте гемы по окружениям (:development, :test), чтобы в продакшен не утекали инструменты разработчика.

Глубже: безопасность и здоровье зависимостей

Управление зависимостями — это не только удобство, но и ответственность, о которой стоит задуматься с самого начала. Каждый добавленный гем — это чужой код, который исполняется в вашем приложении с вашими правами, поэтому к выбору зависимостей нужно подходить разумно. Прежде чем тянуть гем, полезно посмотреть: жив ли проект (когда последний коммит), сколько у него скачиваний и звёзд, нет ли известных уязвимостей. Инструмент bundler-audit сканирует ваш Gemfile.lock на известные дыры безопасности и предупреждает, если какая-то версия скомпрометирована — его стоит запускать в CI. Регулярное обновление зависимостей (bundle outdated покажет, что устарело) защищает от накопления долга и закрывает уязвимости. Обратная сторона — не гнаться за каждым гемом: иногда десять строк своего кода надёжнее, чем зависимость с сотней транзитивных подзависимостей. Зрелый подход — это баланс: переиспользовать проверенные, живые библиотеки для серьёзных задач, но не тащить гем ради того, что пишется в пару строк. Привычка следить за здоровьем зависимостей отличает профессиональный проект от учебного и однажды спасёт вас от неприятного инцидента.

Итог. Гемы — переиспользуемые библиотеки из RubyGems. Bundler по Gemfile разрешает версии и фиксирует их в Gemfile.lock, обеспечивая воспроизводимость. Коммитьте lock-файл и запускайте проектные команды через bundle exec.

Проверьте себя
1. Зачем коммитить Gemfile.lock в репозиторий?
AЭто требование RubyGems
BЧтобы у всей команды и на сервере установился идентичный набор версий гемов
CЧтобы ускорить bundle install
DLock-файл не нужно коммитить
2. Что делает префикс bundle exec перед командой?
AУскоряет команду
BЗапускает команду с версиями гемов, зафиксированными в Gemfile.lock, а не с глобальными
CУстанавливает новые гемы
DУдаляет Gemfile