Ruby: от основ до профессионального уровня

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

1. Введение в Ruby и настройка окружения

Введение в Ruby и настройка окружения

Зачем нужен Ruby и что вас ждёт в курсе

Ruby — это язык программирования общего назначения, известный простым синтаксисом и высокой выразительностью. На Ruby часто пишут веб‑приложения (особенно с Ruby on Rails), автоматизацию, скрипты, инструменты разработчика и небольшие сервисы.

В этом курсе мы будем двигаться от базового синтаксиса и типов данных к профессиональным практикам:

  • работа с пакетами и зависимостями
  • тестирование
  • отладка и профилирование
  • стиль кода и архитектурные приёмы
  • работа с сетью, файлами, потоками
  • Эта статья — стартовая: вы установите Ruby, научитесь проверять окружение и запустите первую программу.

    Что такое интерпретатор Ruby

    Ruby-код выполняет интерпретатор — программа, которая читает ваш файл .rb и выполняет инструкции. Поэтому для работы вам нужно:

  • установленный Ruby (интерпретатор)
  • менеджер пакетов RubyGems (обычно идёт вместе с Ruby)
  • Bundler для управления зависимостями проекта (как правило, уже установлен или ставится одной командой)
  • !Как связаны ваш код, интерпретатор Ruby и установка библиотек через RubyGems/Bundler

    Выбор способа установки

    Главная рекомендация для обучения и профессиональной работы: используйте менеджер версий Ruby. Он позволяет:

  • устанавливать несколько версий Ruby параллельно
  • переключаться между версиями для разных проектов
  • избежать конфликтов с системным Ruby (особенно на macOS и Linux)
  • Популярные варианты:

  • rbenv (часто выбирают за простоту)
  • asdf (универсальный менеджер версий для разных языков)
  • Ниже показаны рабочие пути для основных ОС.

    Установка на macOS

    Вариант A: через Homebrew и rbenv

  • Установите Homebrew (если ещё не установлен):
  • Homebrew
  • Установите rbenv и ruby-build:
  • Инициализируйте rbenv в вашей оболочке.
  • Для zsh (обычно по умолчанию в macOS):

  • Установите Ruby (пример — версия 3.3.0) и сделайте её глобальной:
  • Если вы не уверены, какую версию ставить, берите последнюю стабильную из официальных релизов:

  • Официальный сайт Ruby
  • Установка на Linux

    На Linux лучше избегать установки Ruby из системного пакетного менеджера (например, apt install ruby), потому что версии там часто устаревшие. Надёжнее — поставить через rbenv.

    Вариант A: rbenv (универсально)

  • Установите зависимости для сборки Ruby из исходников (названия пакетов могут отличаться в зависимости от дистрибутива).
  • Для Ubuntu/Debian часто используют:

  • Установите rbenv и ruby-build по инструкции из репозитория:
  • rbenv на GitHub
  • ruby-build на GitHub
  • После установки и инициализации выполните:
  • Установка на Windows

    На Windows самый простой и стабильный путь для начала — RubyInstaller.

  • Скачайте установщик и установите Ruby:
  • RubyInstaller for Windows
  • Во время установки убедитесь, что включена опция добавления Ruby в PATH, чтобы команды ruby, gem были доступны в терминале.
  • После установки откройте новый терминал (PowerShell или cmd) и проверьте версии.
  • Проверка, что всё работает

    Откройте терминал и выполните:

    Ожидаемо:

  • ruby -v показывает версию Ruby, например ruby 3.3.0 ...
  • gem -v показывает версию RubyGems
  • Если команда не найдена:

  • на Windows проверьте PATH и переоткройте терминал
  • на macOS/Linux проверьте, что инициализация rbenv добавлена в конфиг вашей оболочки (~/.zshrc, ~/.bashrc)
  • Первая программа на Ruby

    Создайте файл hello.rb:

    Запустите:

    Разберём строку:

  • puts — команда, которая выводит текст и добавляет перевод строки
  • строка в двойных кавычках — это текст (строка), который мы печатаем
  • Интерактивная консоль IRB

    IRB — интерактивная консоль Ruby. В ней удобно проверять идеи, экспериментировать с кодом и быстро учиться.

    Запуск:

    Пример:

    Выход обычно командой:

    Библиотеки (gems), RubyGems и Bundler

    Что такое gem

    Gem — это библиотека или инструмент, упакованный для установки в Ruby. Установка делается командой gem.

    Пример установки библиотеки:

    rake — популярный инструмент для задач сборки и автоматизации.

    Зачем нужен Bundler

    Если gem install ... ставит библиотеку “в целом в систему/текущую среду”, то Bundler фиксирует, какие именно версии библиотек нужны конкретному проекту, и позволяет воспроизводимо устанавливать зависимости.

    Bundler использует файл Gemfile.

    Проверка Bundler:

    Если Bundler не найден:

    Официальная документация:

  • Bundler
  • Рекомендованная структура рабочего проекта

    Для учебных задач достаточно простого подхода:

  • отдельная папка под проект
  • Ruby-файлы .rb внутри
  • по мере усложнения — добавим Gemfile, тесты и структуру каталогов
  • Пример:

    Редактор кода

    Подойдёт любой редактор, но важно, чтобы были подсветка синтаксиса и удобный запуск терминала.

    Часто выбирают:

  • Visual Studio Code
  • RubyMine
  • Sublime Text
  • Выбор редактора не влияет на изучение языка, поэтому используйте тот, который вам удобен.

    Частые проблемы и быстрые решения

  • Команда ruby не находится
  • Убедитесь, что Ruby установлен и добавлен в PATH (Windows) или что активирован rbenv (macOS/Linux)
  • После установки новой версии через rbenv терминал всё равно показывает старую
  • Закройте и откройте терминал заново, затем проверьте ruby -v
  • Ошибки сборки Ruby на Linux
  • Обычно не хватает системных библиотек для сборки; поставьте зависимости и повторите установку
  • Что дальше по курсу

    Дальше мы начнём писать Ruby-код системно: переменные, типы данных, методы, условия и циклы. Важно, чтобы окружение уже работало — тогда всё внимание будет на языке и практике.

    2. Синтаксис и базовые конструкции языка

    Синтаксис и базовые конструкции языка

    После установки Ruby и проверки окружения (как в предыдущей статье) важно научиться читать и писать код на Ruby: как устроены выражения, переменные, условия, циклы и блоки. В этой статье мы соберём базовый «словарь» Ruby, чтобы дальше уверенно переходить к методам, классам, модулям и профессиональным практикам.

    Официальная документация Ruby: Ruby Documentation.

    Общие правила синтаксиса Ruby

    Ruby читабелен: минимум «шума»

    Ruby старается быть лаконичным:

  • точка с запятой ; обычно не нужна
  • круглые скобки у вызовов методов часто опускают
  • код читается сверху вниз как набор выражений
  • Пример (всё это корректный Ruby):

    Пробелы и переносы строк

    Пробелы чаще всего не влияют на выполнение, но влияют на читаемость. В Ruby принято:

  • 2 пробела на уровень отступа
  • выравнивать код так, чтобы границы блоков были очевидны
  • Комментарии

    Однострочный комментарий начинается с #:

    Многострочный комментарий часто делают так:

    Выражения, значения и nil

    Всё — выражение

    В Ruby почти всё возвращает значение. Например, if тоже возвращает значение:

    nil — «ничего»

    nil означает отсутствие значения.

    Истина и ложь (truthiness)

    В Ruby ложными считаются только:

  • false
  • nil
  • Все остальные значения считаются истинными (включая 0, пустую строку "" и пустой массив []).

    Переменные и присваивание

    Локальные переменные

    Локальные переменные начинаются со строчной буквы или _:

    Именование и стиль

    В Ruby принят стиль snake_case:

    Параллельное присваивание

    Можно присваивать сразу несколько значений:

    Базовые типы и литералы

    Числа

    Строки

    Одинарные кавычки — почти «буквально», двойные — с интерполяцией и спецпоследовательностями:

    Полезные методы строк:

    Символы (Symbol)

    Символ — лёгкий неизменяемый идентификатор, часто используется как ключ в хешах:

    Массивы (Array)

    Хеши (Hash)

    Диапазоны (Range)

    Диапазоны удобно использовать в проверках и циклах:

    Вызов методов и скобки

    В Ruby вызов метода выглядит так: receiver.method(arguments).

    Но скобки у аргументов часто опускают:

    Важно: иногда скобки помогают избежать неоднозначности и повышают читаемость.

    Операторы и сравнения

    Часто используемые операторы:

    | Категория | Операторы | Пример | |---|---|---| | Арифметика | +, -, *, /, % | 5 % 2 | | Сравнение | ==, !=, <, >, <=, >= | age >= 18 | | Логика | &&, ||, ! | admin && active | | Проверка на nil | nil? | value.nil? |

    Полезная конструкция — ||=: «присвой, если ещё не задано/ложно».

    Условия: if, unless, case

    if и elsif

    Также есть постфиксная форма:

    unless — «если не»

    Удобно для простых проверок:

    case — множественный выбор

    Циклы и итерации

    while и until

    while выполняется, пока условие истинно:

    until — наоборот: выполняется, пока условие ложно:

    each и перечисления

    В Ruby чаще используют не «ручные» циклы, а итераторы:

    Это читается как «для каждого элемента массива сделай…».

    !n| последовательно принимает 10, затем 20, затем 30, под схемой подпись "each вызывает блок для каждого элемента". Стиль: простая учебная инфографика, светлый фон, минимализм. | Визуально показывает, что each передаёт элементы в блок по одному

    times и диапазоны

    Блоки: do ... end и { ... }

    Блок — это кусок кода, который можно передать методу.

    Две формы записи:

    Внутри |n| — параметры блока (что передаёт метод each или map).

    Ввод и вывод: puts, print, gets

    Вывод:

    Ввод из терминала:

    Но gets возвращает строку вместе с символом перевода строки. Часто делают chomp, чтобы убрать его:

    Мини-программа: объединяем базовые конструкции

    Создайте файл app.rb и запустите командой ruby app.rb (как в первой статье):

    В этом примере вы использовали:

  • ввод gets.chomp
  • преобразование типов to_i
  • условие if
  • диапазон (1..n)
  • итерацию each с блоком
  • интерполяцию строк #{...}
  • Что дальше по курсу

    Теперь у вас есть базовые строительные блоки Ruby-кода. Следующий шаг — научиться проектировать код через методы, разбирать область видимости переменных, переиспользование логики и постепенно переходить к объектной модели Ruby.

    3. Коллекции, итераторы и работа с блоками

    Коллекции, итераторы и работа с блоками

    Коллекции и итераторы — это то, что делает Ruby особенно удобным для повседневных задач: вы храните данные в массивах и хешах, а затем выражаете обработку данных через методы вроде map, select, reduce и цепочки вызовов.

    В прошлой статье вы уже использовали each и блоки. Здесь вы системно разберёте:

  • как устроены основные коллекции Ruby
  • что такое модуль Enumerable и почему он так важен
  • как работают блоки, что они возвращают и как передаются в методы
  • как писать свои методы, которые принимают блок
  • Официальные справочники (полезно держать открытыми):

  • Класс Array
  • Класс Hash
  • Модуль Enumerable
  • Класс Enumerator
  • Коллекции в Ruby

    Array

    Array — упорядоченная коллекция элементов. Индексация с нуля.

    Полезные операции:

    Hash

    Hash — коллекция пар ключ → значение. Чаще всего ключи — символы.

    Важно помнить:

  • ключи сравниваются по eql? и hash, поэтому ключ должен быть стабильным (обычно символы и строки подходят)
  • Hash не обязан быть «только строками» или «только символами», но смешивание ухудшает читаемость
  • Range

    Range — диапазон значений, чаще всего чисел.

    Диапазоны часто используют для итераций и проверок.

    Итераторы и модуль Enumerable

    Что такое итератор

    Итератор — это метод, который «обходит» элементы коллекции и вызывает переданный блок для каждого элемента или выдаёт элементы по определённому правилу.

    Самый базовый итератор:

    Почему Enumerable так важен

    Enumerable — это модуль, который добавляет десятки методов для обработки последовательностей: map, select, reject, find, reduce, any?, all? и другие.

    Ключевая идея:

  • если объект умеет выдавать элементы через each, то он часто может использовать Enumerable
  • Array и Hash включают Enumerable, поэтому все эти методы доступны «из коробки»
  • Пример на массиве:

    Пример на хеше: важно понимать, что each у Hash по умолчанию выдаёт пары ключ-значение.

    !Иллюстрация цепочки обработки данных через select/map/reduce

    Самые важные методы Enumerable на практике

    Ниже — минимальный «профессиональный набор», который вы будете применять постоянно.

    each

    each предназначен для побочных эффектов: печать, запись в файл, накопление статистики в переменной.

    map

    map преобразует элементы и возвращает новый массив.

    Если вам не нужен новый массив, чаще всего map использовать не стоит.

    select и reject

    select оставляет элементы, для которых блок возвращает истинное значение.

    reject делает противоположное.

    find

    find (он же detect) возвращает первый элемент, который подходит.

    Если не найдено — вернётся nil.

    reduce

    reduce (он же inject) «сворачивает» коллекцию в одно значение.

    Здесь:

  • 0 — начальное значение аккумулятора acc
  • на каждом шаге блок возвращает новое значение acc
  • У reduce много применений: сумма, произведение, склейка строк, построение хеша, группировка.

    each_with_index

    Частая задача — иметь и значение, и индекс.

    Метод chain: читаемая обработка данными

    Ruby позволяет писать цепочки вызовов. Главное правило: каждый шаг должен быть понятен.

    Блоки: что это и как они работают

    Блок как «переданный кусок кода»

    В Ruby блок можно передать методу двумя способами:

    Блок не является обычным объектом сам по себе, но метод может:

  • вызвать его через yield
  • принять его как объект Proc через параметр &block (об этом ниже)
  • Что возвращает блок

    Блок, как и другие выражения Ruby, возвращает значение последнего выражения.

    Управление потоком внутри блока: next и break

    next пропускает текущую итерацию.

    break завершает итерацию. В некоторых итераторах break может передать наружу значение.

    Область видимости и замыкания

    Блок «видит» локальные переменные снаружи и может их менять — это называется замыкание.

    Это мощно, но важно не злоупотреблять: иногда понятнее использовать reduce.

    !Показано, как метод вызывает блок через yield и получает результат

    Как писать методы, которые принимают блок

    yield

    Если ваш метод должен выполнить блок — используйте yield.

    Здесь repeat не знает, что именно делать на каждом шаге — это решает вызывающий код.

    block_given?

    Если блок необязателен, обязательно проверяйте block_given?.

    &block и вызов block.call

    Иногда блок нужно:

  • передать дальше в другой метод
  • сохранить в переменную
  • вызвать несколько раз в разных местах
  • Тогда блок принимают как Proc через &block.

    Практическое правило:

  • используйте yield, если нужно просто вызвать блок «здесь и сейчас»
  • используйте &block, если блок нужно обрабатывать как объект
  • Enumerator и ленивые вычисления

    Когда появляется Enumerator

    Если вызвать each (или другой итератор) без блока, Ruby часто вернёт Enumerator — объект, который можно перебирать позже.

    Enumerator полезен, когда вы хотите:

  • передать «перебор» как значение
  • настроить цепочку обработки и выполнить её позже
  • lazy

    lazy делает цепочку вычислений ленивой: элементы будут обрабатываться только по мере необходимости.

    Здесь (1..) — бесконечный диапазон. Без lazy такую цепочку нельзя было бы безопасно выполнить.

    Мини-практика: обработка данных как в реальных задачах

    Допустим, у нас есть список заказов и нужно посчитать сумму оплаченных.

    В этом примере вы применили типичный для Ruby стиль:

  • хранить данные в Array и Hash
  • выбирать нужное через select
  • сворачивать результат через reduce
  • Что дальше по курсу

    Вы уже умеете работать с данными и выражать обработку через блоки и итераторы. Следующий логичный шаг — структурировать код: выносить логику в методы, группировать в классы, понимать область видимости и проектировать интерфейсы так, чтобы они хорошо сочетались с блоками и Enumerable.

    4. ООП в Ruby: классы, модули, миксины

    ООП в Ruby: классы, модули, миксины

    В предыдущих статьях вы научились работать с коллекциями, итераторами и блоками, а также увидели, насколько удобно выражать обработку данных через методы Enumerable. Теперь следующий шаг к профессиональному Ruby — объектно-ориентированное программирование (ООП): как структурировать код с помощью классов, как переиспользовать поведение через модули и как Ruby «склеивает» функциональность с помощью миксинов.

    Полезные справочники:

  • Класс Class
  • Класс Module
  • Класс Object
  • Модуль Kernel
  • Модуль Enumerable
  • Объекты и сообщения: базовая идея ООП в Ruby

    Ruby — полностью объектный язык: числа, строки, массивы, хеши — всё объекты. ООП в Ruby удобно понимать как модель:

  • объект хранит состояние (данные)
  • объект отвечает на сообщения (вызовы методов)
  • Пример «сообщения»:

    Слева объект-получатель (receiver), справа метод. Ruby ищет метод по правилам поиска метода (об этом ниже).

    Классы: шаблон для создания объектов

    Определение класса и создание экземпляра

    Класс описывает:

  • какие данные хранит объект
  • какие методы доступны
  • User.new создаёт экземпляр класса (объект).

    Инициализация: метод initialize

    initialize вызывается автоматически при new.

    @nameпеременная экземпляра: это состояние конкретного объекта.

    Чтение и запись состояния: attr_reader, attr_writer, attr_accessor

    Чтобы не писать однотипные методы вручную, Ruby даёт генераторы аксессоров:

    Важно: attr_accessor :role создаёт два метода: role и role=.

    Методы экземпляра и self

    Внутри метода экземпляра self — это текущий объект.

    Методы класса и «фабрики»

    Иногда нужно поведение не для конкретного объекта, а для самого класса: например, конструктор-«фабрика».

    def self.guest — это метод класса.

    Константы и области видимости имён

    Константы обычно пишут в CamelCase.

    User::DEFAULT_ROLE использует оператор :: для доступа к константе в области видимости класса.

    Инкапсуляция: public, protected, private

    Инкапсуляция — это управление тем, какие методы доступны снаружи.

  • public можно вызывать извне
  • private нельзя вызывать с явным получателем
  • protected обычно используют, когда объектам одного класса нужно вызывать методы друг друга
  • Пример с private:

    validate_amount! — внутренняя деталь реализации. Снаружи она не должна быть частью интерфейса.

    Наследование: переиспользование через иерархию

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

    super: вызов реализации родителя

    super вызывает метод с таким же именем выше по цепочке наследования.

    Поиск метода: как Ruby решает, что вызвать

    Когда вы вызываете obj.some_method, Ruby ищет метод в определённом порядке:

  • сначала в классе объекта
  • затем в подключённых модулях (миксинах)
  • затем в родительских классах и их модулях
  • в конце — в базовых классах (Object, Kernel, BasicObject)
  • Посмотреть этот порядок можно через ancestors.

    !Наглядный порядок, в котором Ruby ищет методы при вызове

    Модули: два назначения

    Module в Ruby используют в двух основных ролях.

    Пространство имён (namespace)

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

    Примесь поведения (mixin)

    Модуль может содержать методы, которые затем «подмешиваются» в классы.

    include добавляет методы модуля как методы экземпляра.

    Миксины: include, extend, prepend

    include: методы модуля становятся методами экземпляра

    Подходит, когда вы хотите, чтобы каждый объект класса имел это поведение.

    extend: методы модуля становятся методами объекта (часто класса)

    Если сделать extend внутри класса, методы модуля станут методами класса.

    prepend: модуль встаёт перед классом в цепочке поиска

    Это инструмент для аккуратного «оборачивания» поведения (например, логирование или метрики), потому что методы модуля будут вызваны раньше методов класса.

    Практическое правило:

  • include — основа для миксинов с методами экземпляра
  • extend — добавить методы конкретному объекту, чаще всего классу
  • prepend — когда нужно перехватить вызовы и работать через super
  • Связь с Enumerable: как Ruby строит «богатые» интерфейсы

    В прошлой статье вы активно использовали методы Enumerable (map, select, reduce). Это пример миксина: модуль Enumerable добавляет много методов, но требует, чтобы класс реализовал each.

    Минимальный пример своего класса, который получает методы Enumerable:

    Ключевые идеи здесь:

  • include Enumerable «включает» готовые методы
  • достаточно определить each, чтобы получить остальное
  • enum_for(:each) возвращает Enumerator, если блока нет (это продолжает тему Enumerator из прошлой статьи)
  • Когда выбирать класс, модуль или наследование

    В Ruby чаще предпочитают модули и композицию вместо глубоких иерархий.

  • используйте класс, когда нужен новый тип объектов с состоянием и поведением
  • используйте модуль-namespace, когда нужно сгруппировать код и избежать конфликтов имён
  • используйте модуль-миксин, когда нужно переиспользовать поведение между разными классами
  • используйте наследование, когда связь действительно «является» (Dog является Animal), и базовый класс даёт устойчивый, общий контракт
  • Что дальше по курсу

    Теперь вы умеете упаковывать логику в классы и переиспользовать поведение через миксины. Следующий шаг к профессиональному уровню — научиться проектировать интерфейсы классов, писать устойчивый код через исключения и тестирование, а также разбирать более тонкие аспекты Ruby-объектной модели (например, singleton-методы и метапрограммирование) там, где это действительно оправдано.

    5. Исключения, I/O, файлы и работа с JSON

    Исключения, I/O, файлы и работа с JSON

    Когда вы пишете Ruby-код «по-настоящему», вы почти всегда взаимодействуете с внешним миром:

  • читаете ввод пользователя и печатаете результат
  • открываете файлы, сохраняете отчёты, читаете конфиги
  • отправляете или принимаете данные в формате JSON
  • Внешний мир нестабилен: файл может отсутствовать, данные могут быть повреждены, права доступа — ограничены. Поэтому профессиональный Ruby-код строится вокруг двух идей:

  • обрабатывать ошибки предсказуемо (исключения)
  • аккуратно работать с ресурсами (потоки ввода-вывода, файлы)
  • Полезная документация:

  • Exception (Ruby core)
  • IO (Ruby core)
  • File (Ruby core)
  • JSON (Ruby stdlib)
  • Исключения в Ruby

    Что такое исключение

    Исключение — это объект, который сигнализирует об ошибке или о ситуации, когда выполнение «по плану» невозможно.

    Пример: деление на ноль.

    Ruby поднимет исключение (ZeroDivisionError) и прервёт выполнение, если вы его не обработаете.

    Базовый шаблон обработки: begin / rescue / ensure

  • begin задаёт «защищённый» участок.
  • rescue ловит исключение указанного типа.
  • => e сохраняет объект исключения в переменную.
  • ensure выполняется всегда: и при успехе, и при ошибке.
  • !Показывает, как управление переходит в rescue при ошибке и почему ensure выполняется всегда

    Какие исключения ловить: StandardError и «не всё подряд»

    Чаще всего ошибки приложения — это подклассы StandardError. Если вы пишете общий обработчик, обычно ловят именно его.

    Практическое правило:

  • ловите ожидаемые исключения (например, Errno::ENOENT для отсутствующего файла)
  • не делайте «пустые» rescue без логирования и без понятного решения проблемы
  • Поднимать исключения самому: raise

    Иногда ошибка — это часть вашей бизнес-логики: входные данные не прошли проверку.

    Здесь:

  • ArgumentError означает «неверный аргумент»
  • сообщение помогает быстро понять причину
  • Свои классы ошибок

    В небольших скриптах достаточно встроенных исключений. В приложениях удобно вводить доменные ошибки.

    Плюсы:

  • легче отличать «ошибки предметной области» от системных
  • проще писать точечные rescue там, где это нужно
  • Важные поля объекта исключения

  • backtrace особенно важен при отладке: показывает путь выполнения до ошибки
  • I/O в Ruby: ввод-вывод и потоки

    Что такое I/O

    I/O (input/output) — это работа с потоками данных:

  • ввод: клавиатура, файл, сеть
  • вывод: экран, файл, сеть
  • В Ruby многое завязано на класс IO.

    STDIN, STDOUT, STDERR

    Три стандартных потока:

  • STDIN — откуда читаем (обычно клавиатура)
  • STDOUT — куда пишем обычный вывод
  • STDERR — куда пишем ошибки
  • Пример:

    Обратите внимание на &. (safe navigation): если gets вернёт nil, вызов chomp не произойдёт.

    Работа с файлами

    Быстрые операции: File.read и File.write

    Если файл небольшой, проще всего читать и писать целиком.

  • mode: "w" перезаписывает файл
  • для добавления в конец используют mode: "a"
  • Открытие файла с блоком: правильное закрытие ресурса

    Надёжный подход — File.open с блоком: файл закроется автоматически.

    Это особенно важно, потому что открытый файл — ограниченный ресурс.

    Чтение построчно

    Для больших файлов лучше читать построчно.

  • foreach читает файл по строкам и не грузит всё содержимое в память
  • Обработка типичных ошибок файловой системы

    Самые частые ситуации:

  • файла нет
  • нет прав доступа
  • путь некорректный
  • Пример точечной обработки:

    Errno::ENOENT и Errno::EACCES — системные исключения (ошибки ОС), которые Ruby «поднимает» как исключения.

    !Помогает запомнить типовые ветки ошибок при работе с файлами

    JSON в Ruby

    Что такое JSON и зачем он нужен

    JSON — текстовый формат обмена данными, похожий на объекты и массивы:

  • объект JSON похож на Hash
  • массив JSON похож на Array
  • В Ruby JSON находится в стандартной библиотеке, но подключается явно.

    Преобразование Ruby-объектов в JSON: JSON.generate

    Важно:

  • ключи-хеши в JSON всегда становятся строками
  • не любой объект можно сериализовать «как есть»; чаще сериализуют простые структуры (Hash, Array, строки, числа, true/false, nil)
  • Парсинг JSON в Ruby: JSON.parse

    По умолчанию ключи — строки. Если вам удобнее символы:

    Обработка ошибок JSON

    Если строка не является валидным JSON, будет исключение JSON::ParserError.

    Чтение и запись JSON-файлов

    Типичный сценарий: прочитать JSON из файла, распарсить, изменить, записать обратно.

  • JSON.pretty_generate делает форматированный JSON, удобный для чтения человеком
  • File.exist? позволяет аккуратно обработать ситуацию «файла ещё нет» без исключения
  • Мини-сценарий: CLI-скрипт с устойчивостью к ошибкам

    Ниже пример программы, которая:

  • читает путь к JSON-файлу из аргументов командной строки
  • пытается распарсить JSON
  • выводит количество элементов, если это массив
  • корректно сообщает об ошибках
  • Здесь вы применяете всё, что было в курсе ранее:

  • базовый синтаксис и условия
  • коллекции (Array) и проверку типов
  • блоки и аккуратную работу с ресурсами (в других примерах — через File.open)
  • и теперь — исключения как механизм контролируемых ошибок
  • Что дальше

    Теперь вы умеете:

  • отличать «обычные» ветвления логики от ошибок, которые стоит оформлять исключениями
  • безопасно работать с файлами и потоками
  • сериализовать и парсить JSON, обрабатывая ошибки формата
  • Следующий шаг к профессиональному уровню — тестирование и проектирование кода так, чтобы такие сценарии (ошибки I/O, некорректные данные) проверялись автоматически и не ломали приложение в продакшене.

    6. Тестирование и качество кода: RSpec, RuboCop

    Тестирование и качество кода: RSpec, RuboCop

    В предыдущих статьях курса вы научились писать рабочий Ruby-код, структурировать его через классы и модули, а также работать с исключениями, файлами и JSON. На профессиональном уровне важно не только чтобы работало, но и чтобы код:

  • не ломался при изменениях
  • был читаемым и единообразным в команде
  • находил ошибки как можно раньше
  • Для этого обычно используют:

  • автотесты (в Ruby чаще всего на RSpec)
  • статический анализ и стиль (RuboCop)
  • Полезные официальные ресурсы:

  • RSpec
  • Документация RSpec Expectations
  • RuboCop
  • Bundler
  • Зачем нужны тесты

    Тесты отвечают на вопрос: мы ничего не сломали?

    Хорошие тесты помогают:

  • безопасно рефакторить (менять внутренности, сохраняя поведение)
  • документировать поведение кода (через примеры)
  • быстрее ловить регрессии (ошибки, появившиеся после изменений)
  • Практическая мысль: тесты — это не про “покрытие строк”, а про проверку важных правил и сценариев.

    Зачем нужен RuboCop

    RuboCop решает другую задачу: он помогает поддерживать качество кода ещё до запуска.

    Он умеет:

  • проверять стиль (отступы, длину строк, имена переменных)
  • находить потенциальные ошибки (неиспользуемые переменные, подозрительные конструкции)
  • подсказывать более идиоматичный Ruby
  • автоматически исправлять часть нарушений
  • Тесты отвечают: правильно ли ведёт себя программа? RuboCop отвечает: насколько аккуратно и безопасно она написана?

    !Показывает типичный цикл разработки: сначала проверяем поведение тестами, затем качество кода линтером

    Подготовка проекта: Bundler, Gemfile, группы зависимостей

    Даже для учебного проекта полезно оформить зависимости через Bundler.

  • Создайте папку и инициализируйте Bundler:
  • Добавьте зависимости в Gemfile:
  • Здесь важно:

  • group :development, :test означает, что гемы нужны для разработки и тестов, но не обязательно для продакшен-запуска
  • require: false у RuboCop означает, что он не будет автоматически подключаться через require, потому что обычно RuboCop запускают как CLI-инструмент
  • Установите гемы:
  • RSpec: базовые идеи и структура тестов

    RSpec — фреймворк для тестирования, который описывает поведение через примеры.

    Как обычно устроены файлы

    Чаще всего структура такая:

  • lib/ — код приложения
  • spec/ — тесты
  • Пример минимальной структуры:

    Инициализация RSpec

    Обычно появятся:

  • spec/spec_helper.rb (общие настройки)
  • .rspec (опции запуска)
  • Минимальный пример: тестируем метод

    Сделаем простой класс в lib/sum.rb:

    Теперь тест в spec/sum_spec.rb:

    Запуск:

    Ключевые элементы:

  • RSpec.describe Sum — описываем, что тестируем
  • describe ".call" — группируем примеры для метода класса
  • it "..." — конкретный пример поведения
  • expect(...).to eq(...) — ожидание результата
  • expect { ... }.to raise_error(...) — ожидание исключения
  • context: разделение сценариев

    Если у метода много веток, удобно группировать сценарии:

    context — это всё тот же describe, но по смыслу читается как в таком-то контексте.

    let и before: подготовка данных

    Часто нужно подготовить объекты для тестов.

    let создаёт значение лениво: оно вычисляется только если реально используется в примере.

    before выполняет код перед каждым примером в группе.

    Практическое правило:

  • чаще предпочитайте let, потому что он делает тесты более локальными и понятными
  • before используйте, когда нужно именно действие подготовки (например, заполнить временный файл)
  • Что стоит тестировать в рамках курса

    Для учебных проектов и типовых задач этого курса чаще всего достаточно проверять:

  • чистую бизнес-логику (методы, которые возвращают значения)
  • ошибки и валидацию (исключения)
  • работу с коллекциями (например, фильтрация и агрегации)
  • I/O (файлы, сеть) лучше изолировать: вынести в отдельный слой и тестировать минимально, чтобы тесты не были медленными и нестабильными.

    RuboCop: стиль, проверки, автоисправление

    Запуск RuboCop

    Запуск из проекта:

    Если есть нарушения, RuboCop покажет:

  • файл и строку
  • правило (cop), которое нарушено
  • рекомендацию
  • Автоисправление

    Часть проблем можно исправить автоматически:

    Важно:

  • автоисправление не гарантирует идеальный результат, но отлично экономит время на рутине
  • после автоисправления стоит снова запустить тесты
  • Конфигурация через .rubocop.yml

    В корне проекта можно создать .rubocop.yml и настроить правила под команду или учебный стиль.

    Пример, где меняем максимальную длину строки:

    Документация по настройке: RuboCop Configuration

    Типичные проблемы, которые RuboCop помогает найти

  • неиспользуемые переменные
  • сложные методы (слишком длинные или слишком ветвистые)
  • неконсистентный стиль (пробелы, кавычки, отступы)
  • неидиоматичные конструкции Ruby
  • Рекомендуемый рабочий процесс

    Чтобы и поведение, и качество кода держались на уровне, удобно выработать привычку:

  • Вносите изменения небольшими порциями.
  • Запускайте тесты:
  • Запускайте RuboCop:
  • Исправляйте замечания, при необходимости используйте:
  • Запускайте тесты повторно.
  • Если вы используете CI, тот же набор команд обычно запускают на сервере при каждом пуше. Документация по GitHub Actions: GitHub Actions

    Что дальше по курсу

    В следующих шагах профессионального Ruby вы будете усиливать практики качества:

  • проектировать код так, чтобы его было проще тестировать (разделение логики и I/O)
  • подключать дополнительные инструменты (форматирование, проверки безопасности)
  • строить стабильные пайплайны проверки в CI
  • Но уже на текущем уровне связка RSpec + RuboCop даёт базовый, очень прикладной “пояс безопасности” для вашего Ruby-кода.

    7. Профессиональные практики: Bundler, гемы, оптимизация, архитектура

    Профессиональные практики: Bundler, гемы, оптимизация, архитектура

    Вы уже умеете писать Ruby-код, работать с коллекциями и блоками, проектировать классы и модули, обрабатывать исключения и писать тесты с RSpec, а также поддерживать стиль с RuboCop. Следующий шаг к профессиональному уровню — научиться собирать проекты так, чтобы они были:

  • воспроизводимыми на любой машине
  • управляемыми по зависимостям
  • измеримо быстрыми
  • архитектурно понятными и тестируемыми
  • Эта статья связывает практики в единое целое: Bundler и гемы для зависимостей, базовые подходы к оптимизации производительности и рабочие приёмы архитектуры в Ruby-проектах.

    Полезные источники:

  • Bundler
  • RubyGems
  • RubyGems.org
  • Semantic Versioning
  • Benchmark (Ruby stdlib)
  • Bundler как стандарт управления зависимостями

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

  • на компьютерах нескольких разработчиков
  • на CI
  • на сервере
  • Базовые элементы: Gemfile и Gemfile.lock

    В проектах с Bundler обычно есть два ключевых файла.

  • Gemfile описывает какие зависимости нужны (и какие версии допустимы).
  • Gemfile.lock фиксирует какие именно версии были выбраны и установлены.
  • Практическое правило: Gemfile.lock нужно коммитить в репозиторий для приложений и большинства CLI-инструментов, чтобы окружение было воспроизводимым.

    Типовой рабочий цикл

  • Инициализируйте проект:
  • Добавьте зависимости в Gemfile, например:
  • Установите зависимости:
  • Запускайте команды через Bundler:
  • Почему это важно: bundle exec гарантирует, что будет использован набор гемов из вашего Gemfile.lock, а не случайные версии, установленные в системе.

    !Как Gemfile и Gemfile.lock превращаются в воспроизводимое окружение

    Версионирование зависимостей и оператор ~>

    Ruby-проекты обычно используют идеи семантического версионирования: версия выглядит как MAJOR.MINOR.PATCH.

  • MAJOR — потенциально несовместимые изменения
  • MINOR — новая функциональность без поломок совместимости
  • PATCH — исправления без изменения публичного поведения
  • Оператор ~> в Gemfile называется пессимистичным ограничением.

    Примеры:

    Это означает: разрешены версии 3.13.x, 3.14.x, и так далее, но не 4.0.

    Это означает: разрешены только 2.2.8, 2.2.9, 2.2.10 и так далее, но не 2.3.0.

    Выбор ограничения — это баланс:

  • слишком жёстко зафиксировали версии — сложно обновляться
  • слишком свободно — можно неожиданно получить несовместимость
  • Группы зависимостей

    Bundler позволяет разделять зависимости по группам.

    Типичные группы:

  • development — инструменты разработчика
  • test — тестовые зависимости
  • production — зависимости для запуска на сервере
  • Пример:

    На сервере часто ставят только нужные группы, чтобы не тянуть лишние инструменты.

    Обновления: аккуратно и предсказуемо

    Bundler даёт две важные команды.

  • bundle update обновит зависимости (в рамках ограничений в Gemfile) и перепишет Gemfile.lock.
  • bundle update some_gem обновит только конкретный гем и его дерево зависимостей.
  • Профессиональная привычка: обновлять зависимости небольшими шагами и запускать тесты после каждого обновления.

    Binstubs: удобные исполняемые файлы

    Чтобы не писать каждый раз bundle exec ..., можно создать binstubs.

    Например:

    После этого в проекте появятся файлы в bin/, и можно запускать:

    Эти команды автоматически используют окружение проекта.

    Гемы как единицы переиспользования

    Gem — это упакованная библиотека или инструмент.

    Что важно на практике:

  • выбирайте гемы с живой поддержкой и понятной документацией
  • фиксируйте версии через Bundler
  • минимизируйте число зависимостей, особенно для маленьких утилит
  • Standard library и гемы

    В Ruby есть стандартная библиотека, но многие её части подключаются явно.

    Пример:

    Это не гемы вашего приложения, но они всё равно являются зависимостями на уровне Ruby-окружения.

    Как правильно подключать гем

    Если гем нужен как библиотека, обычно достаточно require.

    Пример:

    Если гем нужен как CLI-инструмент, его обычно запускают командой (через bundle exec или bin/...), а require в коде может быть не нужен. Поэтому часто встречается:

    Оптимизация: измеряйте, затем улучшайте

    Производительность — это не только скорость, но и память, и стабильность времени выполнения.

    Профессиональный подход строится так:

  • сначала измеряем текущую ситуацию
  • затем улучшаем узкие места
  • затем повторяем измерение
  • Мини-замеры через Benchmark

    В Ruby есть модуль Benchmark.

    Пример сравнения двух подходов:

    Что вы получаете:

  • время выполнения каждого сценария
  • возможность принять решение на фактах, а не по ощущениям
  • Важно: один прогон может быть шумным. Если результат сомнительный, повторяйте замер несколько раз.

    Типовые источники замедлений в Ruby-коде

  • лишние аллокации объектов (создание большого числа временных массивов и строк)
  • лишние проходы по коллекциям
  • неоптимальные структуры данных для задачи
  • смешивание I/O и вычислений, из-за чего сложно измерять и ускорять
  • Практичные приёмы без фанатизма

    #### Выбирайте правильный итератор

  • map создаёт новый массив
  • each подходит для побочных эффектов
  • select создаёт новый массив с отфильтрованными элементами
  • reduce сворачивает в одно значение
  • Если вам не нужен новый массив, map почти всегда будет лишним.

    #### Делайте вычисления поточными для больших данных

    Если данные большие, цепочки select.map.select.map могут создавать много временных массивов. Иногда лучше:

  • использовать lazy
  • или объединять шаги в один проход
  • Пример с lazy:

    #### Уменьшайте число временных строк

    Пример: при накоплении строки часто выгоднее использовать << вместо постоянной конкатенации через +.

    #### Мемоизация для дорогих вычислений

    Если метод дорогой и результат для объекта не меняется, используйте мемоизацию:

    Смысл: вычисление выполнится один раз, затем будет использоваться кэш.

    Архитектура: как сделать код понятным и тестируемым

    Под архитектурой в рамках Ruby-проекта мы будем понимать практики, которые помогают:

  • отделять бизнес-логику от ввода-вывода
  • контролировать зависимости
  • поддерживать проект по мере роста
  • Разделение ответственности: логика отдельно, I/O отдельно

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

  • слой вычислений и правил
  • слой чтения файлов, аргументов CLI, печати, сети
  • Это сразу помогает тестированию:

  • бизнес-логику легко тестировать без файлов
  • I/O можно тестировать точечно или подменять
  • Пример структуры небольшого проекта

    Один из рабочих вариантов структуры для учебного CLI-проекта:

  • bin/ — точка входа (CLI)
  • lib/ — классы и модули приложения
  • spec/ — RSpec-тесты
  • Пример:

    bin/my_app может быть тонким:

    А основная логика живёт в lib/ и тестируется отдельно.

    Внедрение зависимостей через initialize

    Чтобы код был гибким и тестируемым, зависимости лучше передавать в объект, а не создавать внутри.

    Пример: класс, которому нужен объект для чтения данных.

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

    Явные границы ошибок

    Из статьи про исключения вы знаете приём с доменными ошибками. Он хорошо сочетается с архитектурой:

  • слой I/O ловит системные ошибки (Errno::...)
  • слой доменной логики поднимает понятные ошибки предметной области
  • Это делает поведение предсказуемым и тестируемым.

    Минимальная многослойность для небольших проектов

    Для большинства задач курса достаточно трёх уровней:

  • domain — правила и вычисления
  • application — оркестрация сценария, вызывает domain
  • infrastructure — файлы, сеть, внешние зависимости
  • !Простая модель слоёв и направления зависимостей

    Как всё связать с тестированием и стилем

    Из предыдущей статьи про RSpec и RuboCop вы уже знаете базовый рабочий процесс. На практике он хорошо дополняется тем, что разобрано здесь:

  • Bundler фиксирует зависимости и делает запуск команд воспроизводимым
  • архитектура отделяет логику от I/O, и тесты становятся стабильнее
  • оптимизация начинается с измерений, а не с догадок
  • Минимальный набор команд, который полезно запускать регулярно:

    Если проект структурирован и зависимости управляются корректно, вы получаете профессиональную базу: проект проще поддерживать, проще проверять, проще развивать.