Бывает так, что запускаешь тесты, а они падают там, где вроде бы всё должно работать. В логах — только сухая ошибка, без контекста. Открываешь код, смотришь на участок кода, где произошёл сбой, и начинаешь гадать:
не те входные данные из-за плохого контракта
возникла неучтённая крайняя ситуация
логика в принципе неверная, хотя для большинства случаев всё «вроде бы ок».
Ситуация осложняется тем, что по какой-то причине нет отладчика (pry, byebug и т.п.). Начинаешь на скорую руку выводить переменные puts-ами, но понимание проблемы все равно не приходит.
Вот тут‑то и выручает binding.irb — встроенный в Ruby способ запустить интерактивную консоль IRB прямо в точке вызова.
Что за Binding?
Binding — это встроенный класс Ruby, позволяющий захватить и сохранить контекст выполнения кода в определённый момент времени. Метод binding (также встроен в Ruby) возвращает экземпляр этого класса.
3.2.0 :002 > obj = binding
=> #<Binding:0x000000010e35b718>
3.2.0 :003 > obj.class
=> Binding
Становится доступен метод irb, который запускает IRB (интерактивная среда выполнения кода Ruby).
Для работы с Ruby можно написатьirb, и вы попадете в ту же интерактивную среду.
~ irb
3.2.0 :001 > name = "John"
=> "John"
3.2.0 :002 > age = 35
=> 35
3.2.0 :003 > start_info = "#{name} is #{age} years old"
=> "John is 35 years old"
3.2.0 :004 > name.class
=> String
3.2.0 :006 > start_info
=> "John is 35 years old"
3.2.0 :007 > exit
~
Кто знаком с JavaScript, узнает Node.js REPL (Read‑Eval‑Print Loop).
Как дебажим?
Устанавливаем binding.irb в той части кода, где хотим остановить выполнение. Запускаем и попадаем в консоль IRB.
Смотрим на входные данные:
1: def call_user(name)
=> 2: binding.irb
3: puts "Hello #{name}"
4: end
5:
6: call_user("Larisa")
3.2.0 :001 > name
=> "Larisa"
3.2.0 :002 > exit
Hello Larisa
Проверяем попадание в условие:
def call_user(name)
if name.length < 2
binding.irb
puts "Name is too short"
end
puts "Hello #{name}"
end
call_user("Larisa")
Вызываем необходимые методы:
1: def call_user(name)
2: if name.length < 2
=> 3: binding.irb
4: puts "Name is too short"
5: end
6:
7: puts "Hello #{name}"
8: end
3.2.0 :001 > name.length
=> 1
1: def call_user(name)
2: if name.length < 2
=> 3: binding.irb
4: puts "Name is too short"
5: end
6:
7: puts "Hello #{name}"
8: end
3.2.0 :001 > name.class
=> String
Доступны любые манипуляции с контекстом для нахождения ошибки.
Недавняя история из жизни: валидация входных данных не охватывала один пограничный случай. Первичные тесты проходили, но падали совершенно в другом месте. Надо было разобраться "быстро и еще вчера".
Поставила
binding.irbна входные данные в самом начале цепочки. Необходимая ошибка не рейзилась, данные уходили дальше и валили тесты позднее.
Просто? Да.
Быстро? Не всегда, т.к. зачастую распутывать приходится большие клубки зависимостей.
Итого
binding.irb — элементарный, встроенный и эффективный способ дебажить на лету, без лишних настроек и гемов. Он не просто показывает переменные — он открывает нам контекст выполнения, позволяя буквально заглянуть внутрь кода.
Такое видение контекста помогает понять, что действительно происходит, а не должно происходить. Возможность видеть и анализировать - это основа, остальные инструменты (pry, byebug и т. п.) лишь расширяют эту возможность.