Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Структура гемов и проектов Ruby: Настройка окружения

Bundler появился в Ruby значительно позже RubyGems. Из-за этого устройство библиотек (гемов) и проектов немного отличается. В этом уроке мы немного заглянем внутрь гемов, чтобы понять принципы их работы. Изучим команду bundle gem, которая создает структуру для нового гема и которая, с небольшими модификациями, подходит для своих проектов. Она в себя уже включает настроенный линтер, тесты и интеграцию с Github Actions.

Для работы прикладных проектов в Ruby ничего кроме Bundler не нужно. Достаточно создать Gemfile, указать зависимости и подключить Bundler в своем коде. Дальше можно не отвлекаться и писать прикладной код.

Для гемов все чуть сложнее. У гемов есть метаданные, например, версия, имя, описание и т.п. У гемов есть зависимости, которые должны учитываться RubyGems при установке. Для всего этого нужен специальный файл, называемый спецификацией гема. Кроме того, у гемов есть определенные соглашения по организации кода, которые важны для удобной работы c require.

Гем можно создать полностью руками, но лучше воспользоваться готовой командой bundle gem, которая сделает все сама. Ниже мы пройдемся по всем этапам создания гема и изучим организацию кода внутри. Все это поможет понять работу Ruby и научит эффективно организовывать разработку как гемов, так и своих проектов.

bundle gem

Начнем с генерации. Для этого запустим команду bundle gem, которая задаст несколько вопросов и затем создаст директорию с названием гема и нужными файлами внутри.

# hexletgem - произвольное имя гема
bundle gem hexletgem

Creating gem 'hexletgem'...
Do you want to generate tests with your gem?
Future `bundle gem` calls will use your choice. This setting can be changed anytime with `bundle config gem.test`.
# Выбираем как фреймворк либо rspec либо minitest
# Здесь рассматриваем minitest, чтобы лучше разобраться в основах
Enter a test framework. rspec/minitest/test-unit/(none): minitest

Do you want to set up continuous integration for your gem? Supported services:
* CircleCI:       https://circleci.com/
* GitHub Actions: https://github.com/features/actions
* GitLab CI:      https://docs.gitlab.com/ee/ci/
* Travis CI:      https://travis-ci.org/

Future `bundle gem` calls will use your choice. This setting can be changed anytime with `bundle config gem.ci`.
# Выбираем Github Actions как самый удобный вариант при хостинге на Github
Enter a CI service. github/travis/gitlab/circle/(none): github

# Да, если ваш код не коммерческий
Do you want to license your code permissively under the MIT license?
This means that any other developer or company will be legally allowed to use your code for free as long as they admit you created it. You can read more about the MIT license at https://choosealicense.com/licenses/mit. y/(n): y

# Полезная штука, выбираем да
Codes of conduct can increase contributions to your project by contributors who prefer collaborative, safe spaces. You can read more about the code of conduct at contributor-covenant.org. Having a code of conduct means agreeing to the responsibility of enforcing it, so be sure that you are prepared to do that. Be sure that your email address is specified as a contact in the generated code of conduct so that people know who to contact in case of a violation. For suggestions about how to enforce codes of conduct, see https://bit.ly/coc-enforcement. y/(n): y

# Тут смотрите сами
Do you want to include a changelog?
A changelog is a file which contains a curated, chronologically ordered list of notable changes for each version of a project. To make it easier for users and contributors to see precisely what notable changes have been made between each release (or version) of the project. Whether consumers or developers, the end users of software are human beings who care about whats in the software. When the software changes, people want to know why and how. see https://keepachangelog.com y/(n): y

# Линтер нужен обязательно, ставим rubocop
Do you want to add a code linter and formatter to your gem? Supported Linters:
* RuboCop:       https://rubocop.org
* Standard:      https://github.com/testdouble/standard

Future `bundle gem` calls will use your choice. This setting can be changed anytime with `bundle config gem.linter`.
Enter a linter. rubocop/standard/(none): rubocop

# Немного изучите структуру, посмотрите содержимое файлов
Initializing git repo in /private/tmp/hexletgem
      create  hexletgem/Gemfile
      create  hexletgem/lib/hexlet/example.rb
      create  hexletgem/lib/hexlet/example/version.rb
      create  hexletgem/hexletgem.gemspec
      create  hexletgem/Rakefile
      create  hexletgem/README.md
      create  hexletgem/bin/console
      create  hexletgem/bin/setup
      create  hexletgem/.gitignore
      create  hexletgem/test/test_helper.rb
      create  hexletgem/test/hexlet/example_test.rb
      create  hexletgem/.github/workflows/main.yml
      create  hexletgem/LICENSE.txt
      create  hexletgem/CODE_OF_CONDUCT.md
      create  hexletgem/CHANGELOG.md
      create  hexletgem/.rubocop.yml
Gem 'hexletgem' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html

Спецификация

Спецификация гема описывается Ruby кодом. Внутри нее что-то уже заполнено автоматически, но есть места где стоит TODO. Их нужно заполнить, иначе с кодом не получится работать, он начнет просить заполнения.

# Показана только та часть где есть TODO
Gem::Specification.new do |spec|
  spec.summary = "TODO: Write a short summary, because RubyGems requires one."
  spec.description = "TODO: Write a longer description or delete this line."
  spec.homepage = "TODO: Put your gem's website or public repo URL here."
  spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"

  spec.metadata["homepage_uri"] = spec.homepage
  spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
  spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
end

Для работы с гемом нужно сделать две вещи:

  1. Заполнить summary и description. Для простоты просто сотрите TODO.
  2. Все остальные TODO закомментировать включая homepage_uri, который ссылается на homepage

Зависимости

RubyGems в своей работе опирается только на спецификацию гема. Сюда входят и зависимости. То есть все что нужно для работы гема, должно быть указано в gemspec-файле:

Gem::Specification.new do |spec|
  # Uncomment to register a new dependency of your gem
  # spec.add_dependency "example-gem", "~> 1.0"
  # spec.add_dependency "another-gem", "> 3.2"
end

Здесь же указывались и зависимости для разработки, но с появлением Bundler это изменилось. Все зависимости, необходимые во время разработки и тестирования, переехали в Gemfile.

source 'https://rubygems.org'

# Specify your gem's dependencies in hexletgem.gemspec
gemspec

gem 'rake', '~> 13.0'
gem 'minitest', '~> 5.0'
gem 'rubocop', '~> 1.21'

И чтобы не дублировать зависимости самого гема, в Bundler добавили функцию gemspec (в примере выше), которая подтягивает рантайм-зависимости из спецификации в Gemfile.

Следующий шаг в разработке гема — установка зависимостей. Для этого выполняется команда bin/setup:

bin/setup

bundle install
+ bundle install
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Using rake 13.0.6
Using ast 2.4.2
Using bundler 2.2.32
Using hexletgem 0.1.0 from source at `.`
Using minitest 5.15.0
Using rexml 3.2.5
Using unicode-display_width 2.1.0
Using parallel 1.21.0
Using parser 3.1.1.0
Fetching rainbow 3.1.1
Using ruby-progressbar 1.11.0
Fetching regexp_parser 2.2.1
Fetching rubocop-ast 1.16.0
Installing rubocop-ast 1.16.0
Installing regexp_parser 2.2.1
Installing rainbow 3.1.1
Using rubocop 1.25.1
Bundle complete! 4 Gemfile dependencies, 14 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

# Do any other automated setup that you need to do here

После установки зависимостей можно начинать разрабатывать, запускать линтер, тесты и другие команды. Начнем с линтера. У Rubocop есть свой исполняемый файл rubocop. Каким образом его запустить? Правильный способ через команду bundle exec. Эта команда запускает исполняемые файлы из зависимостей текущего проекта игнорируя глобально установленные программы.

bundle exec rubocop

Inspecting 8 files
......C.

Offenses:

test/hexletgem_test.rb:5:7: C: [Correctable] Style/ClassAndModuleChildren: Use nested module/class definitions instead of compact style.
class HexletgemTest < Minitest::Test
      ^^^^^^^^^^^^^^^^^^^

8 files inspected, 1 offense detected, 1 offense auto-correctable

Точно так же запускаются любые другие утилиты, которые устанавливаются как зависимости в проект.

Rake

Еще один важный элемент экосистемы Ruby - Rake. Rake это Ruby + Make, фреймворк для описания и запуска разнообразных задач связанных с проектом. Список доступных задач можно посмотреть через утилиту rake:

# -T - список задач
bundle exec rake -T

rake build                 # Build hexletgem-0.1.0.gem into the pkg directory
rake build:checksum        # Generate SHA512 checksum if hexletgem-0.1.0.gem into the checksums directory
rake clean                 # Remove any temporary products
rake clobber               # Remove any generated files
rake install               # Build and install hexletgem-0.1.0.gem into system gems
rake install:local         # Build and install hexletgem-0.1.0.gem into system gems without network access
rake release[remote]       # Create tag v0.1.0 and build and push hexletgem-0.1.0.gem to TODO: Set to your gem server...
rake rubocop               # Run RuboCop
rake rubocop:auto_correct  # Auto-correct RuboCop offenses
rake test                  # Run tests

Эти задачи не встроены в rake, они добавлены, во время генерации гема, в файл Rakefile.

# Задачи связанные с публикацией гема
require "bundler/gem_tasks"
# Подгружает Rake::TestTask, которым описывается запуск тестов ниже
require "rake/testtask"

# Задача rake test для запуска тестов
Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.libs << "lib"
  t.test_files = FileList["test/**/*_test.rb"]
end

# Задачи для запуска линтера
require "rubocop/rake_task"

# Задача по умолчанию, запускается так: bundle exec rake
task default: %i[test rubocop]

Часть задач подгружается из гемов, но некоторые описаны прямо здесь, например запуск тестов. Rubocop тоже присутствует здесь, то есть линтер можно запускать и так:

# В данном случае идентично bundle exec rubocop
bundle exec rake rubocop
......C.

Rake важная часть экосистемы Ruby. Практически ни один проект не обходится без него. Например, в Rails из коробки идет несколько десятков таких команд. Плюс всегда можно написать свои.

Структура проекта

Код гема находится в директории lib и организован определенным образом:

lib
├── hexletgem
│   └── version.rb
└── hexletgem.rb

Внутри lib лежит Ruby-файл и директория по имени гема. Ruby-файл hexletgem.rb содержит входной модуль в гем, в нашем случае он называется Hexletgem:

require_relative 'hexletgem/version'

module Hexletgem
  class Error < StandardError; end
  # Your code goes here...
end

Именно через этот модуль пользователи гема будут получать доступ к его содержимому. Это важное соглашение, которое позволяет интуитивным образом понимать, как можно взаимодействовать с гемом после его включения в проект:

Имя гема Имя модуля Имя файла
rake Rake lib/rake.rb
faraday Faraday lib/faraday.rb
faker Faker lib/faker.rb
slim_lint SlimLint lib/slim_lint.rb
web-console Web::Console lib/web/console.rb

Директория lib/hexletgem содержит файл version.rb в котором указана версия гема:

module Hexletgem
  VERSION = "0.1.0"
end

Все остальные файлы с кодом располагают рядом с этим файлом, внутри директории hexletgem и ее поддиректориях.

Превращение гема в проект

Генерация гема создает удобную структуру не только для разработки гемов, но и для прикладных проектов. Для готовности нужно сделать два шага:

  1. Удалить файл с версией
  2. Удалить gemspec-файл

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 5 840 ₽ в месяц
Создание веб-приложений со скоростью света
5 месяцев
c опытом
Старт 28 марта

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу