Основы языка 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 + 2→1.+(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 разрабатывался с двумя ключевыми целями:
- Удовлетворить потребности программиста — сделать процесс написания кода интеллектуально и эстетически приятным;
- Избавить от избыточной рутины — избегать дублирования, излишней формальности и принудительных ограничений, не обоснованных необходимостью.
Этот подход материализуется в 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), и его экземплярами (request → self.class.request).
Порядок поиска методов
При вызове obj.foo интерпретатор идёт по цепочке ancestors (от первого сработавшего к BasicObject):
- Singleton class объекта (методы только этого экземпляра).
- Класс объекта.
- Модули, подключённые через
prepend(в обратном порядке подключения) — перед методами класса. - Суперкласс и его
prepend-модули — рекурсивно. - Модули, подключённые через
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 поведение может быть инкапсулировано тремя основными способами:
- Блок — синтаксическая конструкция, передаваемая при вызове метода; не является объектом;
Proc— полноценный объект классаProc, хранящий замыкание, допускающий нестрогую арность и семантикуreturn, аналогичную блоку;- Лямбда — тоже объект
Proc, но с семантикой, приближенной к методу — строгая арность иreturn, выходящий только из лямбды, а не из окружающего контекста.
Эти различия не академичны — они напрямую влияют на стабильность и предсказуемость программ.
Семантика return
Рассмотрим поведение return в трёх контекстах:
Код ITЗагрузка примера кода…
- В лямбде
returnведёт себя как в методе: завершает только лямбду, возвращая управление вызывающему коду (в данном случае — вg). - В
Procreturnзавершает весь метод, в котором был вызван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. |
| Динамические интерфейсы, адаптеры к внешним API | method_missing + respond_to_missing? | Позволяет выразить предметную область без дублирования методов; требует осторожности и кэширования. |
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.