Статьи

Рубиисты, пришло время отрицать себя от IRB!

tOTeiZo

Каждый Rubyist знает об irb . Интерактивная оболочка Ruby по сути представляет собой REPL (цикл чтения-оценки-печати). Введите какое-нибудь выражение, и результат будет немедленно возвращен. Так как же подходит Прай? Прай считает себя мощной альтернативой стандартной оболочке IRB. Но это намного, намного больше, чем это.

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

Давай попробуем!

Установка

Получить Прай просто:

 % gem install pry pry-doc Fetching: pry-0.9.12.2.gem (100%) Successfully installed pry-0.9.12.2 Parsing documentation for pry-0.9.12.2 Installing ri documentation for pry-0.9.12.2 Fetching: pry-doc-0.4.6.gem (100%) Successfully installed pry-doc-0.4.6 Parsing documentation for pry-doc-0.4.6 Installing ri documentation for pry-doc-0.4.6 2 gems installed 

Здесь мы устанавливаем как pry и pry-doc . pry-doc предоставляет документацию и исходный код MRI Core. Нам это понадобится в следующих примерах.

Теперь просто чтобы убедиться, что все работает:

 % pry -v Pry version 0.9.12.2 on Ruby 2.0.0 

На момент написания статьи я использовал последнюю версию Pry. Прай отлично работает как на Ruby 1.9, так и на Ruby 2.0. Обратите внимание, что я использую Ruby MRI.

Часть I: Давайте разберемся в некотором коде

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

Давайте запустим наш сеанс Pry:

 % pry [1] pry(main)> 

Показ документации с помощью show-doc

Допустим, мне нужно вспомнить разницу между Array#map Array#map! , Команда show-doc делает это несложно:

 [2] pry(main)> show-doc Array#map From: array.c (C Method): Owner: Array Visibility: public Signature: map() Number of lines: 11 Invokes the given block once for each element of self. Creates a new array containing the values returned by the block. See also Enumerable#collect. If no block is given, an Enumerator is returned instead. a = [ "a", "b", "c", "d" ] a.map { |x| x + "!" } #=> ["a!", "b!", "c!", "d!"] a #=> ["a", "b", "c", "d"] [3] pry(main)> show-doc Array#map! From: array.c (C Method): Owner: Array Visibility: public Signature: map!() Number of lines: 10 Invokes the given block once for each element of self, replacing the element with the value returned by the block. See also Enumerable#collect. If no block is given, an Enumerator is returned instead. a = [ "a", "b", "c", "d" ] a.map! {|x| x + "!" } a #=> [ "a!", "b!", "c!", "d!" ] 

Прай предоставляет очень удобный ярлык для show-doc? :

 [3] pry(main)> ? Array#map! 

Навигация по состоянию с помощью cd и ls

Это легко одна из самых крутых особенностей Pry. Допустим, у меня есть массив:

 [4] pry(main)> arr = [1, 2, 3] => [1, 2, 3] 

Мы можем cd в объект, например, так:

 [5] pry(main)> cd arr [6] pry(#<Array>):1> 

Обратите внимание, что когда мы вводим cd в arr , приглашение меняется на pry(# ):1> pry(# ):1> , Это говорит нам о том, что текущий объект является экземпляром Array , обозначенным знаком # . «

Теперь попробуйте ls -ing.

 [6] pry(#<Array>):1> ls Enumerable#methods: all? each_entry find_all max minmax_by sort_by any? each_slice flat_map max_by none? chunk each_with_index grep member? one? collect_concat each_with_object group_by min partition detect entries inject min_by reduce each_cons find lazy minmax slice_before Array#methods: & count include? reject slice * cycle index reject! slice! + delete insert repeated_combination sort - delete_at inspect repeated_permutation sort! << delete_if join replace sort_by! <=> drop keep_if reverse take == drop_while last reverse! take_while [] each length reverse_each to_a []= each_index map rindex to_ary assoc empty? map! rotate to_s at eql? pack rotate! transpose bsearch fetch permutation sample uniq clear fill place select uniq! collect find_index pop select! unshift collect! first pretty_print shelljoin values_at combination flatten pretty_print_cycle shift zip compact flatten! product shuffle | compact! frozen? push shuffle! concat hash rassoc size self.methods: __pry__ locals: _ __ _dir_ _ex_ _file_ _in_ _out_ _pry_ 

Что еще делает ls ? Давайте спросим Прай:

 [14] (pry) main: 0> ls -h Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object] ls [-g] [-l] ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object. The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator. Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class. options: -m, --methods Show public methods defined on the Object (default) -M, --instance-methods Show methods defined in a Module or Class -p, --ppp Show public, protected (in yellow) and private (in green) methods -q, --quiet Show only methods defined on object.singleton_class and object.class -v, --verbose Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling) -g, --globals Show global variables, including those builtin to Ruby (in cyan) -l, --locals Show locals, including those provided by Pry (in red) -c, --constants Show constants, highlighting classes (in blue), and exceptions (in purple) -i, --ivars Show instance variables (in blue) and class variables (in bright blue) -G, --grep Filter output by regular expression -h, --help Show this message. 

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

 [7] pry(#<Array>):1> min => 1 [8] pry(#<Array>):1> max => 3 [9] pry(#<Array>):1> reverse => [3, 2, 1] 

Просмотр источника с помощью show-method

Всегда задумывались, как Array#map! реализовано? Напомним, что поскольку мы уже находимся в объекте, мы можем просто использовать show-source map! вместо show-source Array#map! ,

 [10] pry(#<Array>):1> show-source map! From: array.c (C Method): Owner: Array Visibility: public Number of lines: 12 static VALUE rb_ary_collect_bang(VALUE ary) { long i; RETURN_SIZED_ENUMERATOR(ary, 0, 0, rb_ary_length); rb_ary_modify(ary); for (i = 0; i < RARRAY_LEN(ary); i++) { rb_ary_store(ary, i, rb_yield(RARRAY_PTR(ary)[i])); } return ary; } 

MRI реализован на C, что объясняет слегка загадочный список. Чтобы получить список кода Ruby, вам нужно переключиться на Rubinius , так как большинство его основных библиотек построено на Ruby. Изучение базы кода Rubinius с помощью Pry — отличный способ узнать о деталях реализации Ruby в Ruby вместо C.

Есть несколько других способов достижения того же результата. Но перед этим пойдем на один уровень вверх:

 [11] pry(#<Array>):1> cd .. [12] pry(main)> 

Следующее все эквивалентны:

 [13] pry(main)> show-source Array#map! [14] pry(main)> show-source arr.map! 

Так же, как show-doc и ? эквивалент для show-source равен $ :

 [15] pry(main)> $ Array#map! [16] pry(main)> $ arr.map! 

Часть II: Погружение глубже в средства отладки Прая

До сих пор мы видели, что Pry предоставляет нам очень хорошую систему помощи, а также способы навигации и просмотра состояния наших объектов. Но Прай может сделать намного больше. В этом разделе мы рассмотрим средства отладки Pry.

Предпосылки

Мы должны сказать Прай, каким должен быть наш редактор по умолчанию. Создайте файл с именем .pryrc в вашем домашнем каталоге. Так как я люблю Vim:

 Pry.config.editor = 'vim' 

Если вы хотите продолжить, сейчас самое время взять файл примера:

 class Order def initialize @line_items = [] end def add_line_item(line_item) @line_items << line_item end def total subtotals = @line_items.each { |li| li.quantity * li.price } subtotals.reduce(:+) end end class LineItem attr_reader :quantity, :price def initialize(quantity, price) @price = price @quantity = quantity end end order = Order.new order.add_line_item LineItem.new(2, 3.00) order.add_line_item LineItem.new(4, 1.00) puts order.total 

Вот очень простой класс Order и LineItem . Order может добавить много LineItem , каждый из которых содержит quantity и price . Объект Order также может вычислять total .

Запустите эту программу и посмотрите, как она падает:

 % ruby order.rb order.rb:12:in `each': undefined method `+' for #<LineItem:0x007fb4e2013350 @price=3.0, @quantity=2> (NoMethodError) from order.rb:12:in `reduce' from order.rb:12:in `total' from order.rb:28:in `<main>' 

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

Установка точек останова с помощью binding.pry

Так как мы знаем, что наша программа вылетает в строке 14, давайте поставим точку останова за одну строку до этого. Измените метод totals следующим образом:

 def total subtotals = @line_items.each { |li| li.quantity * li.price } binding.pry # <-- Add this subtotals.reduce(:+) end 

На этот раз мы запускаем нашу программу немного по-другому. Обратите внимание на флаг -r pry :

 % ruby -r pry order.rb From: /Users/rambo/Desktop/store/order.rb @ line 14 Order#total: 12: def total 13: subtotals = @line_items.each { |li| li.quantity * li.price } => 14: binding.pry 15: subtotals.reduce(:+) 16: end [1] pry(#<Order>)> 

Что сейчас произошло? Во-первых, ваша программа не вылетала. Вместо этого был запущен сеанс Pry, и выполнение программы останавливается на том месте, где мы разместили binding.pry — нашу точку останова. После этого видим pry(# )> pry(# )> , что означает, что мы находимся в экземпляре Order . Это связано с тем, что binding.pry запускает сеанс Pry в рамках нашего экземпляра Order .

Давайте вспомним, что делает ls :

 [1] pry(#<Order>)> ls Order#methods: add_line_item line_items total instance variables: @line_items locals: _ __ _dir_ _ex_ _file_ _in_ _out_ _pry_ subtotals 

line_items ли заполнены наши line_items ?

 [2] pry(#<Order>)> line_items => [#<LineItem:0x007f80d25b62b0 @price=3.0, @quantity=2>, #<LineItem:0x007f80d25b6288 @price=1.0, @quantity=4>] 

Все идет нормально. Присмотритесь к total методу. Так как binding.pry после переменной subtotals , мы, безусловно, можем получить доступ к этому:

 [3] pry(#<Order>)> subtotals => [#<LineItem:0x007f80d25b62b0 @price=3.0, @quantity=2>, #<LineItem:0x007f80d25b6288 @price=1.0, @quantity=4>] 

Ага! Результат subtotals line_items точно такой же, как и у line_items . Мы должны были использовать map вместо each ! Помните, как мы настраивали наш редактор ранее? Теперь мы будем использовать это с пользой.

Выполнение edit с

Pry позволяет редактировать метод, не выходя из сеанса. Теперь мы знаем, что должны заменить each на map . Итак, давайте сделаем это:

 [4] pry(#<Order>)> edit total 

Вы заметите, что vim (или любой другой редактор, настроенный вами в .pryrc ) запустится с курсором на первой строке total . Внесите необходимые изменения:

 def total subtotals = @line_items.map { |li| li.quantity * li.price } binding.pry subtotals.reduce(:+) end 

Выйдите из редактора, и вы вернетесь на сеанс Pry. Теперь давайте посмотрим, что содержат subtotals итоги:

 [1] pry(#<Order>)> subtotals => [6.0, 4.0] 

Ницца! Когда мы вышли из нашего редактора, Pry автоматически перезагрузил файл и снова остановился на binding.pry .

Запуск команд оболочки из Pry

Перед удалением binding.pry мы можем проверить, работает ли строка после binding.pry . Так как я знаю номер строки, я продолжу и запусту строку:

 [2] pry(#<Order>)> play -l 15 10.0 

Успех! Теперь мы можем пойти дальше и удалить binding.pry . Но прежде чем мы это сделаем, давайте посмотрим, какие изменения мы внесли:

 [3] pry(#<Order>)> .git diff 

Прай имеет возможность запускать произвольные команды оболочки. Все, что вам нужно сделать, это префикс команды с . (точка), как то, что мы сделали для git diff :

 diff --git a/order.rb b/order.rb index c05aa7d..823ccac 100644 --- a/order.rb +++ b/order.rb @@ -10,7 +10,9 @@ class Order end def total - subtotals = @line_items.each { |li| li.quantity * li.price } + subtotals = @line_items.map { |li| li.quantity * li.price } subtotals.reduce(:+) end end @@ -26,4 +28,4 @@ end order = Order.new order.add_line_item LineItem.new(2, 3.00) order.add_line_item LineItem.new(4, 1.00) puts order.total 

Посмотреть трассировку стека с помощью wtf?

Давайте намеренно вызвать некоторые проблемы. Измените код, добавив еще один LineItem . Чтобы сэкономить нам немного времени, давайте также поставим перед этой строкой binding.pry .

 order = Order.new order.add_line_item LineItem.new(2, 3.00) order.add_line_item LineItem.new(4, 1.00) binding.pry order.add_line_item LineItem.new(1/0, 100) puts order.total 

Затем мы запускаем программу:

 % ruby -r pry order.rb From: /store/order.rb @ line 30 : 25: end 26: 27: order = Order.new 28: order.add_line_item LineItem.new(2, 3.00) 29: order.add_line_item LineItem.new(4, 1.00) => 30: binding.pry 31: order.add_line_item LineItem.new(1/0, 1.00) 32: puts order.total 

Давайте продолжим и сыграем в строку 31:

 [1] pry(#<Order>)> play -l 31 

Как и ожидалось, программа вылетает. Чтобы увидеть подробную трассировку стека, используйте wtf? команда:

 [2] pry(#<Order>)> wtf? Exception: ZeroDivisionError: divided by 0 -- 0: (pry):6:in `/' 1: (pry):6:in `total' 3: /usr/local/var/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/pry-0.9.12.2/lib/pry/pry_instance.rb:328:in `evaluate_ruby' ... 9: /usr/local/var/rbenv/versions/2.0.0-p247/lib/ruby/gems/2.0.0/gems/pry-0.9.12.2/lib/pry/pry_instance.rb:231:in `catch' 

Чтобы увидеть еще более длинную трассировку стека, просто добавьте больше ? , Например, wtf??? дает 30 строк. У разработчиков Pry действительно есть чувство юмора.

Анализ cat --ex стека с помощью cat --ex

Прай все еще имеет еще один трюк в рукаве. cat --ex направляет вас к фактической строке, которая cat --ex исключение:

 [3] pry(main)> cat --ex Exception: ZeroDivisionError: divided by 0 -- From: (pry) @ line 1 @ level: 0 of backtrace (of 15). => 1: order.add_line_item LineItem.new(1/0, 1.00) 

cat --ex также принимает число в качестве дополнительного аргумента, который, по сути, выводит вас на трассировку стека. Эта функция очень полезна для отладки и отслеживания больших программ. (Любой, кто делал какие-либо нетривиальные приложения на Rails, определенно оценит эту функцию.)

Завершение

Я надеюсь, что эта статья убедила вас попробовать Pry (у этого есть хорошее звучание, не так ли?). Есть еще немного материала, который не был покрыт. Например, мы не смотрели, как мы могли бы заменить консоль Rails на Pry. Кроме того, мы не взглянули на плагины Pry , которые добавляют еще более мощные функции к уже впечатляющему набору функций.

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

Проверьте официальную Pry Wiki . Документация очень полная. Есть также несколько ссылок, где вы можете узнать больше о Pry, поэтому я не буду повторять это здесь.

Приятного любопытства!