Перейти к основному содержимому

Основы языка Ruby

Разработчику Архитектору

Play ITЗагрузка интерактивного демо…


Основы языка Ruby

Что такое Ruby?

Ruby — это язык программирования со следующими особенностями:

  • Типизация — динамическая, сильная (на практике — утиная); вывода типов нет; опциональные аннотации — RBS, Sorbet, Steep.
  • Парадигма — мультипарадигменный — чистый объектно-ориентированный (всё — объект), императивный, функциональный (блоки, Enumerable), метапрограммирование.
  • Уровень — высокоуровневый.
  • Выполнение — интерпретируемый: исходник → байт-код YARV → виртуальная машина MRI; альтернативы — JRuby (JVM), TruffleRuby (GraalVM), mruby (встраиваемый).
  • Память — автоматическая (generational mark-and-sweep GC в MRI).
  • Платформа — кроссплатформенный; управляемый runtime (CRuby/MRI); JRuby — на JVM; не транспиляция в другой высокоуровневый язык.
  • Формат разработки — скриптовый: .rb можно запустить без сборки; для проектов — Gemfile, Bundler, структура Rails или аналог.
  • Направление — универсальный; сильные стороны — веб-бэкенд (Rails, Sinatra), автоматизация и DevOps, DSL, скриптинг, инструментарий.
  • REPL — есть — irb в терминале (ruby -e / irb), pry и pry-byebug, консоль Rails (rails console).
  • Поколение — классический (с 1995 года), активно развивающийся (ежегодные релизы, ветка 3.x).
  • Параллелизм и асинхронность — потоки (Thread) и fibers; в MRI GVL ограничивает параллельное выполнение Ruby-кода на CPU; Ractors (Ruby 3, экспериментально) — изоляция без GVL; нативного async/await нет — асинхронность через гемы (async, EventMachine) или I/O в потоках.
  • Безопасность — относительно "опасный" — динамическая типизация, открытые классы, monkey patching, eval и send; нет гарантий memory safety как у Rust; GVL снижает data races в потоках, но не защищает от логических ошибок.

Если какой-то пункт из списка непонятен — подробные определения и примеры в Язык программирования.

Фундамент Ruby после первой программы и синтаксиса. Сначала TL;DR, затем разделы по мере вопросов. Примеры с ActiveRecord — из Rails, не из stdlib.

Кратко (TL;DR)

  • Всё — объекты, операторы — методы (1 + 21.+(2)); ложны только nil и false.
  • Имена методов: ? — предикат, ! — мутация или строгий вариант (save!).
  • Блоки — главный инструмент итерации и RAII (File.open { ... }); отдельно от объектов Proc и lambda.
  • Модули: include / prepend / extend вместо множественного наследования; порядок поиска методов предсказуем (см. ниже).
  • Ключевые аргументы (name:) в Ruby 3 не смешиваются с обычным хэшем без ** — иначе ArgumentError.
  • Параллелизм MRI: потоки помогают при I/O, но GVL ограничивает параллельное выполнение Ruby-кода на CPU — подробнее в Асинхронность.
  • Rails ≠ Ruby: present?, .rescue на объектах, ActiveRecord — гемы/фреймворк, не стандартная библиотека.

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


Философия — минимум неожиданностей, максимум выразительности

Согласно декларации создателя языка Юкихиро Мацумото (Matz), Ruby разрабатывался с двумя ключевыми целями:

  1. Удовлетворить потребности программиста — сделать процесс написания кода интеллектуально и эстетически приятным;
  2. Избавить от избыточной рутины — избегать дублирования, излишней формальности и принудительных ограничений, не обоснованных необходимостью.

Этот подход материализуется в POLS (Principle of Least Surprise, "принцип наименьшего удивления") — культурной норме, а не в формальной спецификации VM. Поведение конструкций должно соответствовать интуитивным ожиданиям разработчика, даже если формально возможны иные интерпретации. Например, метод String#strip, удаляющий пробельные символы по краям строки, возвращает новую строку; вызов strip! (с восклицательным знаком) изменяет исходную строку in place. Это не просто соглашение об именовании — это система метафор, встроенная в язык — операции, потенциально разрушительные для исходного состояния, явно маркируются.

Такой подход снимает когнитивную нагрузку, связанную с необходимостью помнить "как именно в этом языке работает sort — изменяет ли он массив или возвращает копию?". В Ruby ответ предсказуем — если метод возвращает новое значение — его имя "чистое" (sort, map, select); если же он изменяет получателя — к имени добавляется !. Аналогично, методы, возвращающие логическое значение, заканчиваются на ? (nil?, empty?, include?). Это культурная норма, заложенная в стандартную библиотеку и поддерживаемая сообществом на уровне конвенции. Язык делает её логичной и удобной.


Объектно-ориентированная модель как основа всего

Ruby реализует чистую объектно-ориентированную модель. В отличие от языков, где примитивы (числа, символы, true/false) существуют вне иерархии классов, в Ruby всё является объектом, и все операции — вызовами методов. Даже литералы — это синтаксический сахар для создания объектов:

42.class # => Integer
42.even? # => true
42.times { ... } # вызов метода `times` у объекта Integer

true.class # => TrueClass
nil.class # => NilClass

Такая унификация устраняет искусственные границы между "простыми" и "сложными" сущностями. Программист не переключается между режимами "работаю с примитивами" и "работаю с объектами" — он всегда оперирует сущностями, обладающими поведением и состоянием. Это позволяет, например, писать выразительные цепочки вызовов:

" Hello, World! ".strip.downcase.gsub(/world/, 'Ruby')
# => "hello, ruby!"

Каждый шаг — вызов метода у результата предыдущего шага. При этом нет необходимости импортировать отдельные модули для работы со строками — стандартная библиотека уже предоставляет богатый набор методов, а при необходимости — легко расширить класс новыми методами ("monkey patching"), хотя в продакшене к этому прибегают осторожно.

У каждого объекта есть singleton class (eigenclass) — отдельный класс в цепочке поиска методов, куда попадают методы только этого экземпляра. Их можно объявить через def obj.method_name или define_singleton_method. Это скрытый уровень между объектом и его обычным классом. Так можно изменить поведение одного экземпляра, не затрагивая остальные объекты того же класса.


Синтаксис — баланс между лаконичностью и недвусмысленностью

Синтаксис Ruby стремится быть естественным. Он минимизирует формальные элементы, не несущие смысловой нагрузки:

  • Скобки при вызове методов не обязательны (если это не вызывает неоднозначности);
  • Ключевое слово return часто избыточно — метод возвращает значение последнего вычисленного выражения;
  • Блоки кода передаются как неявные параметры с помощью { ... } или do ... end, что делает вызовы функций высшего порядка органичными: array.map { |x| x * 2 }.

Однако эта свобода не ведёт к хаосу. Язык предоставляет управляемую гибкость:

  • Приоритет операторов строго определён (например, and/or имеют более низкий приоритет, чем &&/||, что позволяет использовать их для управления потоком без скобок);
  • Контекстные ключевые слова (if, unless, while, until) могут использоваться как постфиксные модификаторы: puts "Warm" if temperature > 20;
  • Выражения case не ограничены сравнением на равенство — они используют оператор ===, что позволяет сопоставлять с диапазонами, классами, регулярными выражениями и даже пользовательскими условиями.

Такой синтаксис позволяет писать код, близкий к естественному языку:

order.process unless order.canceled?
report.generate if report.data && !report.data.empty?

Метод present? встречается в Rails (ActiveSupport), но не в стандартной библиотеке Ruby. В чистом Ruby для проверки "есть содержимое" обычно пишут data && !data.empty? или явно обрабатывают nil.

Это не просто "читаемость" в формальном смысле — это семантическая прозрачность. Программист, даже не знающий Ruby, может интуитивно понять, что делает этот фрагмент, даже если не знает как это реализовано.


Контекст применения — от скриптов до систем

Ruby часто ассоциируется исключительно с веб-фреймворком Ruby on Rails. Однако его применение гораздо шире — и определяется философией языка.

В первую очередь, Ruby — это язык автоматизации и инструментария. Его стандартная библиотека включает мощные модули для работы с файловой системой (File, Dir), сетью (Net::HTTP, Socket), регулярными выражениями, XML/JSON/YAML. Это позволяет писать компактные скрипты для:

  • подготовки и трансформации данных перед загрузкой в аналитические системы;
  • оркестрации процессов CI/CD (например, через Rake — встроенный инструмент построения задач);
  • генерации конфигурационных файлов, отчётов, документации.

Во-вторых, Ruby — это платформа для построения предметно-ориентированных языков (DSL). Благодаря открытой структуре классов, динамической диспетчеризации и гибкому синтаксису, в Ruby легко создавать внутренние DSL, которые выглядят как декларативные спецификации, а не как код. Примеры:

  • RSpec — фреймворк тестирования, где тесты читаются как спецификации поведения;
  • Rakefile — описание задач сборки в виде блоков task :name do ... end;
  • Capistrano — конфигурация деплоя как последовательность on roles(:app) { ... }.

В-третьих, Ruby поддерживает мультимодальное программирование: один и тот же проект может включать императивные, функциональные и объектно-ориентированные стили — в зависимости от решаемой подзадачи. Методы map, select, inject позволяют писать в функциональном стиле без необходимости отказываться от мутации состояния, где это уместно. Классы и модули поддерживают композицию через include, extend, prepend, что делает наследование не единственным средством повторного использования кода.

Наконец, Ruby демонстрирует баланс между динамизмом и отладочными возможностями. Несмотря на отсутствие статической типизации (в классическом смысле), в языке есть:

  • рефлексия (Object#methods, Object#respond_to?, Object#instance_variable_get);
  • интроспекция (defined?, binding);
  • перехват неопределённых методов (method_missing), что лежит в основе многих DSL.

Эти возможности позволяют строить адаптивные системы — объекты могут изменять свою структуру во время выполнения, классы — динамически подключать поведение, а инструменты (например, pry или debug) — предоставлять глубокий доступ к состоянию программы без её остановки.


Блоки как первоклассные конструкции поведения

Одним из центральных, определяющих Ruby понятий являются блоки — фрагменты кода, передаваемые в методы и выполняемые в их контексте. Блоки не являются объектами языка напрямую, но представляют собой синтаксическую и концептуальную основу, на которой строятся Proc, лямбды и замыкания.

Блок — это выражение поведения, передаваемое как часть вызова метода, без необходимости именования, инкапсуляции в отдельный класс или даже явного объявления переменной. Его синтаксис ({ … } или do … end) интегрирован в саму грамматику языка, что делает его неотделимой частью вызова функции, а не её аргументом в традиционном смысле.

Метод, принимающий блок, может:

  • выполнить его ноль, один или несколько раз (yield);
  • передать параметры внутрь блока (yield value);
  • обернуть его в объект Proc при необходимости (&block);
  • проверить его наличие (block_given?).

Это позволяет реализовывать мощные шаблоны управления потоком, недоступные в языках без нативной поддержки блоков:

File.open('data.txt') do |file|
process(file)
end

Здесь File.open гарантирует, что файл будет закрыт после завершения блока — независимо от того, завершился ли он успешно или с исключением. Такой паттерн ресурс–после–использования ("resource acquisition is initialization", RAII в терминах C++) реализуется в Ruby на уровне библиотеки, а не компилятора, и доступен любому программисту.

Блоки замыкают лексическое окружение:

prefix = "Log:"
["error", "warn"].each { |msg| puts "#{prefix} #{msg.upcase}" }
# => Log — ERROR
# => Log — WARN

Переменная prefix остаётся доступной внутри блока, хотя метод each не имеет к ней никакого отношения. Это свойство лежит в основе функциональных техник — карринга, частичного применения, создания конфигурируемых стратегий.

Важно отметить различие между блоками и Proc/лямбдами:

  • Блок — синтаксическая конструкция, существующая только в момент вызова метода;
  • Proc — объект, хранящий блок, который можно передавать, возвращать, сохранять;
  • Лямбда (-> { … } или lambda { … }) — Proc со строгой проверкой арности и семантикой return, аналогичной методу (return возвращает из лямбды, а не из окружающего метода).

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

l = ->(x) { x * 2 }
l.call(5) # => 10
l.call # ArgumentError: wrong number of arguments

в то время как Proc.new игнорирует избыток и дополняет недостаток nil:

p = Proc.new { |x| x.to_s }
p.call(5) # => "5"
p.call # => ""

Именно поэтому Proc удобен для реализации гибких колбэков (например, в DSL), а лямбды — для точных функциональных преобразований.


Пространства имён, области видимости и управление состоянием

Ruby явно разделяет уровни видимости через префиксы имён переменных:

ПрефиксТипЖизненный циклИнициализированное значение по умолчанию
localЛокальнаяБлок, метод, класс-телоОшибка при обращении до присваивания
@ЭкземпляраОбъектnil
@@КлассаКласс и все его потомки (одно состояние)Обязательна инициализация
$ГлобальнаяВсё приложениеnil
CONSTКонстантаЛексическая область (класс/модуль)Обязательна инициализация

Эта система позволяет точно управлять тем, где и как хранится состояние. Например, константа, объявленная внутри класса, не "засоряет" глобальное пространство имён и не требует полного пути при обращении изнутри этого класса:

class Config
TIMEOUT = 30
def self.timeout_seconds
TIMEOUT # константа видна внутри класса без префикса Config::
end
end

При этом константы могут быть переназначены (с предупреждением), что полезно в тестах или при hot-reload, но требует осознанного подхода.

Ключевое слово self в Ruby динамично: оно всегда ссылается на текущий получатель сообщения. Это может быть:

  • экземпляр класса (внутри метода экземпляра);
  • сам класс (внутри метода класса или class << self);
  • модуль (внутри module_eval).

Такая гибкость позволяет писать код, не дублируя логику между экземплярами и классами:

class API
def self.request(path) get(path) end
def request(path) self.class.request(path) end

private

def self.get(path) "GET #{path}" end
end

Здесь get — приватный метод класса, недоступный извне, но используемый и классом (self.request), и его экземплярами (requestself.class.request).


Порядок поиска методов

При вызове obj.foo интерпретатор идёт по цепочке ancestors (от первого сработавшего к BasicObject):

  1. Singleton class объекта (методы только этого экземпляра).
  2. Класс объекта.
  3. Модули, подключённые через prepend (в обратном порядке подключения) — перед методами класса.
  4. Суперкласс и его prepend-модули — рекурсивно.
  5. Модули, подключённые через include (в обратном порядке) — после класса, перед суперклассом.
МеханизмКуда попадают методыТипичное использование
include Mмежду классом и суперклассоммиксины для экземпляров
prepend Mперед классомперехват и расширение методов класса
extend Mв singleton class"методы класса" (def self.x)

Код ITЗагрузка примера кода…

Инструменты отладки: User.ancestors, User.instance_method(:greet).source_location, obj.method(:greet).owner.

Подробнее про super, prepend и видимость — в ООП в Ruby и управляющие конструкции.


Ключевые аргументы (Ruby 2.x → 3.x)

Начиная с Ruby 2.0, именованные параметры задаются в объявлении метода через name::

def create_user(name:, role: "member", active: true)
{ name: name, role: role, active: active }
end

create_user(name: "Ada", role: "admin")
# => {:name=>"Ada", :role=>"admin", :active=>true}

Ruby 3.0 окончательно развёл ключевые аргументы и позиционный хэш. Типичные ошибки после миграции с 2.7:

def configure(host:, port: 443); end

opts = { host: "example.com", port: 8080 }
configure(opts) # ArgumentError — ожидаются ключи, передан один Hash
configure(**opts) # OK — явное раскрытие хэша в keyword args

def legacy(options = {}); end
configure(**{ host: "x" }) # для keyword-методов нужен **, не голый {}

Правила на практике:

  • в объявлении: def f(a, **kw)kw собирает лишние ключи; def f(a:) — ключ обязателен;
  • при передаче дальше: other(**kwargs);
  • не полагаться на автоматическое "превращение" последнего Hash в keywords — в 3.x это отключено.

Синтаксис ** в вызовах и объявлениях разобран в справочнике (раздел "Методы").


Модули — композиция поведения без наследования

Наследование в Ruby одиночное — и это сознательное ограничение. Вместо множественного наследования Ruby предлагает модули — контейнеры для методов, констант и вложенных классов, которые могут быть включены (include) в классы для расширения их поведения.

Модуль — не "интерфейс" и не "абстрактный класс". Это поведенческий микс, который можно подключать независимо:

Код ITЗагрузка примера кода…

Модули могут определять методы класса через included/extended хуки:

Код ITЗагрузка примера кода…

Такой подход позволяет отделять поведение от идентичности: класс Event не является Timestampable, он обладает способностью проставлять временные метки. Это соответствует принципу композиции над наследованием и минимизирует иерархическую связанность.


Открытые классы и динамическое поведение

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

class String
def blank?
self.strip.empty?
end
end

" ".blank? # => true

Такой код допустим и используется в фреймворках (например, ActiveSupport в Rails). Однако ответственность за последствия лежит на разработчике: изменение глобального поведения может нарушить работу сторонних библиотек.

Более безопасная альтернатива — рефайнменты (refinements), введённые в Ruby 2.0:

Код ITЗагрузка примера кода…

Здесь blank? доступен только внутри Processor, и не затрагивает другие части системы. Это компромисс между выразительностью и изоляцией.


Работа с зависимостями — gem’ы и Bundler

Ruby поставляется со встроенным менеджером пакетов — RubyGems. Пакет (gem) — это упакованный код, метаданные и зависимости, распространяемый как единое целое. Управление версиями и конфликтами решается с помощью Bundler — инструмента, обеспечивающего воспроизводимость окружения.

Файл Gemfile описывает зависимости проекта декларативно:

ruby '3.2.3'
source 'https://rubygems.org'

gem 'http', '~> 5.0'
gem 'json', '>= 2.5', '< 3.0'
gem 'rspec', group: :test

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

  • ~> 5.0 означает "любая версия >= 5.0.0 и < 6.0.0" (т.н. pessimistic version constraint);
  • group позволяет изолировать зависимости по окружениям (разработка, тестирование, продакшен);
  • Gemfile.lock фиксирует точные версии всех gem’ов, включая транзитивные, что гарантирует идентичность сборок на разных машинах.

Особенно важно управление платформами — gem’ы, содержащие нативные расширения (например, nokogiri), могут собираться по-разному на macOS и Linux. Bundler позволяет явно указать поддерживаемые платформы:

bundle lock --add-platform x86_64-linux
bundle install

Это добавляет в Gemfile.lock секцию PLATFORMS и предотвращает ошибки развёртывания в Docker-контейнерах или на серверах.


Блоки, Proc и лямбды — не просто синонимы — три уровня абстракции поведения

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

  1. Блок — синтаксическая конструкция, передаваемая при вызове метода; не является объектом;
  2. Proc — полноценный объект класса Proc, хранящий замыкание, допускающий нестрогую арность и семантику return, аналогичную блоку;
  3. Лямбда — тоже объект Proc, но с семантикой, приближенной к методу — строгая арность и return, выходящий только из лямбды, а не из окружающего контекста.

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


Семантика return

Рассмотрим поведение return в трёх контекстах:

Код ITЗагрузка примера кода…

  • В лямбде return ведёт себя как в методе: завершает только лямбду, возвращая управление вызывающему коду (в данном случае — в g).
  • В Proc return завершает весь метод, в котором был вызван Proc#call. Это позволяет, например, реализовать early-return в DSL:
def with_validation(&block)
return nil unless valid?
block.call
end

но требует осторожности при использовании Proc вне вызова метода (например, в REPL возникает LocalJumpError).

  • В блоке, переданном через yield, return допустим только внутри метода, которому передан блок. Попытка вернуть значение из блока, сохранённого в переменную и вызванного позже, приведёт к ошибке — потому что контекст метода уже завершён.

Эта семантика отражает философию Ruby: блоки и Procрасширения метода, тогда как лямбды — независимые единицы поведения.


Замыкания — захват состояния, а не копирование

Все три конструкции создают лексические замыкания — они захватывают ссылки на переменные из окружающего контекста, а не их значения на момент создания:

def counter
n = 0
-> { n += 1 }
end

c1 = counter
c2 = counter

c1.call # => 1
c2.call # => 1
c1.call # => 2

Каждый вызов counter создаёт новую локальную переменную n, и каждая лямбда замыкается на свою копию. Если бы n был экземплярной переменной — обе лямбды разделяли бы одно состояние.

Это свойство лежит в основе:

  • Фабрик объектов (counter, memoize, debounce);
  • Конфигурируемых стратегий, где параметры захвачены один раз при инициализации:
def throttle(delay:)
last_call = 0
->(*args, &block) do
now = Time.now.to_f
return if now - last_call < delay
last_call = now
block.call(*args)
end
end
  • DSL с внутренним состоянием, например, билдеров:

Код ITЗагрузка примера кода…

Здесь instance_eval(&block) выполняет блок в контексте экземпляра QueryBuilder, и все вызовы where идут в self — благодаря замыканию на self.


Делегирование — Proc#curry, method(:name).to_proc, &:

Ruby поддерживает функциональные техники делегирования через неявное преобразование:

  • &: (to_proc shortcut)
    Символы (Symbol) определяют метод to_proc, который возвращает Proc, вызывающий одноимённый метод у получателя:
[1, 2, 3].map(&:to_s) # эквивалентно [1, 2, 3].map { |x| x.to_s }

Это работает потому, что & вызывает to_proc у аргумента. Чтобы построить proc из символа с параметром, возвращают лямбду из метода (а не цепляют &: к результату with_prefix):

class Symbol
def prefixed_by(prefix)
->(obj) { "#{prefix}#{obj.public_send(self)}" }
end
end

users.map(&(:name).prefixed_by('User: '))
# => ["User: Alice", "User: Bob", ...]
  • curry
    Метод Proc#curry частично применяет аргументы и возвращает новую лямбду, ожидающую оставшиеся:
multiply = ->(a, b) { a * b }
double = multiply.curry.(2)
double.(5) # => 10
  • method(:name)
    Возвращает объект Method, который можно вызывать как Proc:
math = Math.method(:sqrt)
[4, 9, 16].map(&math) # => [2.0, 3.0, 4.0]

Объект Method помнит имя и получателя:

str = "hello"
up = str.method(:upcase)
up.call # => "HELLO"

Эти механизмы позволяют строить гибкие конвейеры без явного объявления промежуточных функций.


method_missing — перехват неопределённых сообщений как инструмент проектирования

В Ruby вызов метода — это отправка сообщения объекту. Если объект не отвечает на сообщение, вызывается method_missing, получая имя метода и аргументы:

Код ITЗагрузка примера кода…

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

  • respond_to_missing? должен быть переопределён, иначе respond_to?(:name) вернёт false, что нарушит контракты многих библиотек (например, ActiveRecord или JSON.generate);
  • method_missing вызывается после поиска в иерархии классов, но до поиска в Kernel;
  • Это — последняя линия обороны; использовать его стоит только когда статическая структура невозможна (например, динамические API, ORM, DSL).

Пример — реализация DSL для HTTP-запросов

Код ITЗагрузка примера кода…

Вывод:

→ GET /users
→ POST /posts (body: {"title":"Hello"})

Здесь method_missing позволяет выразить намерение непосредственно: get, postглаголы предметной области.


Производительность и кэширование

Повторный вызов method_missing для одного и того же имени — дорого. На практике применяют динамическое определение методов после первого перехвата:

Код ITЗагрузка примера кода…

После первого вызова fetch_user(123) создаётся реальный метод fetch_user, и последующие вызовы не проходят через method_missing. Это паттерн, известный как define_method memoization.


Практические рекомендации по выбору конструкции

СценарийРекомендуемая конструкцияОбоснование
Итерация, управление ресурсами (File.open, DB.transaction)Блок (do … end или { })Естественно выражает "делай это в контексте"; семантика return безопасна внутри метода.
Коллбэки, конфигурация, гибкие интерфейсыProcДопускает нестрогую арность (например, игнорирование дополнительных параметров); return полезен для early-exit.
Функциональные преобразования, композиция, частичное применениеЛямбда (-> { … })Строгая арность предотвращает ошибки; поведение return предсказуемо; совместима с Enumerable#reduce, curry.
Динамические интерфейсы, адаптеры к внешним APImethod_missing + respond_to_missing?Позволяет выразить предметную область без дублирования методов; требует осторожности и кэширования.

Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.