Pry est une alternative à la console interactive IRB.
IRB fait très bien son travail de console interactive (REPL) mais j’ai toujours eu l’impression qu’il manquait quelques petits plus pour qu’IRB soit parfait.
Pry vient combler ces manques en nous offrant :
Pour commencer à utiliser Pry, je vous conseille de l’installer de manière globale :
$ gem install pry pry-doc
Commençons par lancer Pry et testons ses possibilités :
$ pry
Je passe ici sur toutes les fonctionnalités basiques de Pry qui se comportent exactement comme IRB.
Pry nous permet donc de lister la définition d’une méthode, qu’elle ait été écrite en Ruby ou en C :
pry(main)> show-method Array#first
From: array.c (C Method):
Number of lines: 11
Owner: Array
Visibility: public
static VALUE
rb_ary_first(int argc, VALUE *argv, VALUE ary)
{
if (argc == 0) {
if (RARRAY_LEN(ary) == 0) return Qnil;
return RARRAY_PTR(ary)[0];
}
else {
return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_FIRST);
}
}
pry(main)> show-method FileUtils.rm_rf
From: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/1.9.1/fileutils.rb @ line 644:
Number of lines: 6
Owner: #Class:FileUtils
Visibility: public
def rm_rf(list, options = {})
fu_check_options options, OPT_TABLE['rm_rf']
options = options.dup
options[:force] = true
rm_r list, options
end
Je trouve cette fonctionnalité extrêmement pratique au quotidien. Il m’arrive souvent de vouloir parcourir le code de Gems que j’utilise. Maintenant plus besoin d’ouvrir les sources du gem en se déplaçant dans le bon répertoire et en lançant son éditeur. Avec Pry, il suffit de demander directement la définition d’une méthode.
Pry nous permet également d’avoir un accès à la documentation d’une classe ou d’une méthode donnée :
pry(main)> show-doc Enumerable#any?
From: enum.c (C Method):
Number of lines: 11
Owner: Enumerable
Visibility: public
Signature: any?()
Passes each element of the collection to the given block. The method
returns true if the block ever returns a value other
than false or nil. If the block is not
given, Ruby adds an implicit block of {|obj| obj} (that
is any? will return true if at least one
of the collection members is not false or
nil.
%w{ant bear cat}.any? {|word| word.length >= 3} #=> true
%w{ant bear cat}.any? {|word| word.length >= 4} #=> true
[ nil, true, 99 ].any? #=> true
Encore une fonctionnalité que je trouve très pratique en phase de développement ou de debug, d’autant plus que Pry prend en charge la coloration syntaxique, ce qui facilite beaucoup la lecture depuis la console.
Il est également possible d’appeler la documentation ou la définition d’une méthode directement sur une instance :
pry(main)> s = "lorem"
=> "lorem"
pry(main)> show-doc s.each_char
From: string.c (C Method):
Number of lines: 11
Owner: String
Visibility: public
Signature: each_char()
str.each_char {|cstr| block } -> str
str.each_char -> an_enumerator
Passes each character in str to the given block, or returns
an enumerator if no block is given.
"hello".each_char {|c| print c, ' ' }
produces:
h e l l o
Vous pouvez également avoir accès à la documentation RI depuis la console.
pry(main)> show-doc Fixnum#+
From: numeric.c (C Method):
Number of lines: 3
Owner: Fixnum
Visibility: public
Signature: +(arg1)
Performs addition: the class of the resulting object depends on
the class of numeric and on the magnitude of the
result.
Pour ma part je préfère show-doc que je trouve plus lisible et qui a pour avantage de gérer le format de documentation YARD.
Il est possible d’installer un paquet optionnel depuis Pry pour pouvoir créer des Gist directement depuis la console. Bien pratique quand vous travaillez en équipe et souhaitez un regard neuf sur votre code.
Commençons donc par installer la commande :
pry(main)> install-command gist
Nous pouvons, dès lors, utiliser la commande “gist” pour créer des Gist sans sortir de notre session Pry :
gist -m my_method # crée un gist des sources de la méthode donnée
gist -d my_method # crée un gist de la documentation de la méthode donnée
gist -i 1..10 # crée un gist pour les commandes entrée de 1 à 10
gist -c Array # créer un gist pour la classe Array
Ceci n’est qu’un exemple des possibilités. Pour tout savoir sur cette commande, vous pouvez entrer :
help gist
Notez aussi que si vous voulez associer les gists créés à votre compte Github, il faut d’abord vous identifier via :
pry(main)> gist --login
Obtaining OAuth2 access_token from github.
Github username: Bounga
Github password:
Success! https://github.com/settings/applications
L’un des grands atouts de Pry est qu’il permet de naviguer à travers les classes et les objets mais il permet également d’utiliser le shell sans avoir à sortir de la session Pry.
Listons les méthodes de classe de Array :
pry(main)> ls Array -m
Array.methods: [] try_convert
puis les méthodes d’instance :
pry(main)> ls Array -M
Enumerable#methods: all? any? chunk collect_concat detect each_cons each_entry each_slice each_with_index each_with_object entries find find_all flat_map grep group_by inject max max_by member? min min_by minmax minmax_by none? one? partition reduce slice_before sort_by
JSON::Ext::Generator::GeneratorMethods::Array#methods: to_json
Array#methods: & * + - << <=> == [] []= assoc at clear collect collect! combination compact compact! concat count cycle delete delete_at delete_if drop drop_while each each_index empty? eql? fetch fill find_index first flatten flatten! frozen? hash include? index insert inspect join keep_if last length map map! pack permutation place pop pretty_print pretty_print_cycle product push rassoc reject reject! repeated_combination repeated_permutation replace reverse reverse! reverse_each rindex rotate rotate! sample select select! shelljoin shift shuffle shuffle! size slice slice! sort sort! sort_by! take take_while to_a to_ary to_s transpose uniq uniq! unshift values_at zip |
Maintenant déplaçons nous dans des objets ActiveRecord :
pry(main)> Price
=> Price(id: integer, price: float, created_at: datetime, updated_at: datetime, product_item_id: integer, shop_id: integer, minimum_price: float)
pry(main)> cd Price
pry(Price):1> first
=> #<Price id: 112248585, price: 12.9, created_at: "2012-08-27 10:43:32", updated_at: "2012-08-27 10:43:32", product_item_id: 1010763938, shop_id: 459005629, minimum_price: nil>
pry(Price):1> cd first
pry(#<Price>):2> price
=> 12.9
pry(#<Price>):2> nesting
Nesting status:
--
0. main (Pry top level)
1. Price
2. #<Price>
pry(#<Price>):2> jump-to 0
pry(main)>
Il est possible de se déplacer dans votre arborescence et d’exécuter n’importe quelle commande disponible en console :
pry(main)> gem-cd activesupport
/usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/activesupport-3.2.6
pry(main)> .ls
CHANGELOG.md MIT-LICENSE README.rdoc lib
pry(main)> dir = File.dirname(log_file)
=> "/var/log"
pry(main)> .cd #{dir}
pry(main)> .pwd
/var/log
pry(main)> .date
Jeu 30 aoû 2012 17:27:14 CEST
Dans Pry, l’édition sur la ligne de commande n’est pas en reste. Il est par exemple possible modifier une ligne déjà validée précédemment, annuler la saisie courante, sauvez les commandes et résultats dans des fichiers, … :
pry(main)> def helo(name)
pry(main)* puts "Hi #{name}"
pry(main)* show-input
1: def helo(name)
2: puts "Hi #{name}"
pry(main)* amend-line 1 def hello(name)
1: def hello(name)
2: puts "Hi #{name}"
[16] pry(main)* end
=> nil
pry(main)> def test(foo, bar)
pry(main)* puts [foo, bar].join
pry(main)* !
Input buffer cleared!
pry(main)> save-file -m hello ./hello.rb
pry(main)> cat ./hello.rb
def hello(name)
puts "Hi #{name}"
end
pry(main)> save-file -i 1..10 ./hello.rb --append
pry(main)> cat ./hello.rb
def hello(name)
puts "Hi #{name}"
end
Price
first
price
pry(main)> save-file -k show-method ./my_command.rb
pry(main)> cat my_command.rb
c = block_command match, desc, options do |*args|
run action, *args
end
Pry instancie et gère automatiquement un certain nombre de variables locales qui peuvent s’avérer utiles :
Comme pour IRB, il est possible de définir des valeurs par défaut à utiliser au lancement de Pry pour le personnaliser.
Les options à appliquer à Pry peuvent être définies globalement dans le fichier ~/.pryrc
.
Voyons quelles sont les principales options disponibles :
Pry.config.color = true # active / désactive la coloration syntaxique
Pry.config.pager = false # active / désactive le pager pour la doc, les sources, …
Pry.config.auto_indent = false # active / désactive l'indentation automatique
Pry.config.correct_indent = false # active / désactive la correction automatique de l'indentation
Pry.config.command_prefix = "%" # caractère utilisé pour dénoter l'appel d'une commande shell
Pry.config.history.file = "~/.irb_history" # chemin du fichier d'historique
Pry.config.editor = proc { |file, line| "st -w #{file}:#{line}" } # éditeur utilisé
Pry.config.prompt = proc { |obj, nest_level, _| "#{obj}:#{nest_level}> " } # format du prompt
Pry.config.print = proc { |output, value| output.puts "=> #{value.inspect}" } # format d'affichage des valeurs de retour
Pry.config.exception_handler = proc do |output, exception, _|
output.puts "#{exception.class}: #{exception.message}"
output.puts "from #{exception.backtrace.first}"
end # Personnalisation de la réaction aux exceptions
Pour pouvoir utiliser awesome_print en coordination avec Pry, il nous faut modifier la configuration de print
:
begin
require 'awesome_print'
Pry.config.print = proc { |output, value| output.puts value.ai }
rescue LoadError => err
puts "no awesome_print"
end
L’autre grand domaine de prédilection de Pry est le debug. En effet, il s’avère particulièrement bien pensé lorsqu’il s’agit d’inspecter une pile dans un état donné.
On peut soit lancer Pry en chargeant le code a explorer :
$ pry -r ./lib/ma_lib.rb
Ce qui permet ensuite de faire des tests, manipuler les objets, les examiner, etc. On peut également lancer Pry en mode “runtime invocation”, ce qui permet à la façon de n’importe quel debbuger de poser un “breakpoint” qui stoppera l’exécution de la pile et vous passera la main en ouvrant une session Pry dans votre terminal.
Pour ce faire il faut que votre projet utilise Pry (à ajouter en development dans votre Gemfile) et ensuite y faire appel dans votre code. Premièrement, il faut charger Pry avec un simple
require 'pry'
Il ne reste ensuite plus qu’à poser des breakpoint, là où bon vous semble :
binding.pry
À l’exécution, une fois arrivé sur cette ligne, l’interpréteur vous rend la main en vous proposant une session Pry.
Si vous utilisez Pow pour vos développements Rails / Rack, vous n’avez pas de terminal dédié à l’exécution du serveur de votre appli. Il est donc impossible pour Pry de vous rendre la main en mode interactif.
Pour palier à ce problème, un plugin a été développé. Ce plugin permet d’avoir des sessions Pry distantes. On peut donc lancer une appli sans utiliser de shell interactif tout en concervant la possibilité de se connecter (à distance) à une session Pry lorsqu’un breakpoint est rencontré.
Il faut donc installer le gem pry-remote
. Une fois installé, dans le code de votre app, les breakpoint se poseront à l’aide de binding.remote_pry
.
Vous pouvez ensuite dans un terminal, lancer la commande pry-remote qui se chargera de se connecter à la session Pry distante (via DRB).
Dans une session Pry, comme pour IRB, lorsqu’une exception est levée, elle est attrapée par la console. Vous êtes notifié de l’exception qui a été levée, sans plus.
Si vous souhaitez obtenir un extrait de la trace, une commande est mise à votre disposition :
pry(main)> 1 / 0
ZeroDivisionError: divided by 0
from (pry):2:in `/'
pry(main)> wtf?
Exception: ZeroDivisionError: divided by 0
--
0: (pry):2:in `/'
1: (pry):2:in `__pry__'
2: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:275:in `eval'
3: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:275:in `re'
4: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:251:in `rep'
5: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:231:in `block (3 levels) in repl'
6: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:230:in `loop'
7: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:230:in `block (2 levels) in repl'
8: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:229:in `catch'
9: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:229:in `block in repl'
pry(main)> wtf???
Exception: ZeroDivisionError: divided by 0
--
0: (pry):2:in `/'
1: (pry):2:in `__pry__'
2: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:275:in `eval'
3: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:275:in `re'
4: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:251:in `rep'
5: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:231:in `block (3 levels) in repl'
6: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:230:in `loop'
7: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:230:in `block (2 levels) in repl'
8: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:229:in `catch'
9: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:229:in `block in repl'
10: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:228:in `catch'
11: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_instance.rb:228:in `repl'
12: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/pry_class.rb:154:in `start'
13: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/cli.rb:171:in `block in <top (required)>'
14: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/cli.rb:65:in `call'
15: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/cli.rb:65:in `block in parse_options'
16: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/cli.rb:65:in `each'
17: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/lib/pry/cli.rb:65:in `parse_options'
18: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/pry-0.9.10/bin/pry:16:in `<top (required)>'
19: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/bin/pry:23:in `load'
20: /usr/local/Cellar/rbenv/0.3.0/versions/1.9.3-p194/bin/pry:23:in `<main>'
Oui, vous avez bien lu, la méthode en question est wtf?
et plus vous ajouterez de ?
à la fin du nom de méthode, plus elle vous retournera de ligne de contexte.
Vous pouvez également faire un cat
sur l’exception pour en avoir une description succinte vous rappelant le type d’exception levée ainsi que l’appel incriminé :
pry(main)> cat --ex
Exception: ZeroDivisionError: divided by 0
--
From: (pry) @ line 2 @ level: 0 of backtrace (of 20).
1: Toto
=> 2: 1 / 0
Si le code incriminé n’a pas été tapé dans la console mais provient d’un fichier chargé, il est alors possible de l’éditer directement à l’endroit qui a levé l’exception :
pry(main)> edit --ex
Il est à noter que le fichier en question sera rechargé automatiquement dans Pry.
Il est également possible d’éditer le fichier de son choix en ouvrant l’éditeur sur ce dernier à une ligne donnée :
pry(main)> edit hello.rb:10
Pry propose de conserver un historique des commandes entrées, il est ensuite possible de le manipuler pour retrouver ou rejouer des commandes données :
pry(main)> hist
1: Order.where("messages IS NOT NULL")
2: Order.where("messages IS NOT NULL").count
3: Order.with_pending_messages
pry(main)> hist --grep Order
1: Order.where("messages IS NOT NULL")
2: Order.where("messages IS NOT NULL").count
3: Order.with_pending_messages
7: Order.with_pending_messages.all
pry(main)> hist --tail 5
# => retourne les cinq éléments les plus récents
pry(main)> hist --head 5
# => retourne les cinq éléments les plus anciens
pry(main> hist --replay 7000
# => rejoue la commande numéro 7000
pry(main)> hist --replay 7000..7010
# => rejoue les commandes 7000 à 7010
pry(main)> hist --exclude
# => ne contient que les expressions Ruby mais aucune commande Pry
pry(main)> hist --save 4..5 test.rb
Saving history in /Users/cavigneaux/articles/test.rb ...
... history saved.
pry(main)> hist --show 2..5
2: quit
3: Order.where("messages IS NOT NULL")
4: Order.where("messages IS NOT NULL").count
5: reload!
Plusieurs possibilités s’offrent à nous, tout d’abord remplacer purement et simplement IRB par Pry. Il faudra modifier votre Gemfile pour y inclure Pry :
Gemfile: gem "pry", group: "development"
Puis ajouter ceci à votre fichier config/environments/development.rb
:
silence_warnings do
begin
require 'pry'
IRB = Pry
rescue LoadError
end
end
Désormais, en lançant rails console
, c’est en fait un session Pry qui démarrera.
On peut également choisir de garder IRB et d’ajouter une tâche Rake pour lancer une console Pry. Ajoutons donc un fichier lib/tasks/pry.rake
:
desc "Start a Pry console"
task :console do
pry -r ./../config/environment
end
Nous sommes maintenant en mesure de lancer notre session Pry via rake console
.
Pry-rails est un Gem qui permet de faire cette manipulation pour vous. Il ajoute en plus quelques méthode pour pouvoir facilement lister les routes, les models, …
Une simple ligne à ajouter à votre Gemfile et vous voilà sous Pry.
Une fois qu’on a goûté à Pry, il est assez difficile de revenir en arrière et d’utiliser à nouveau IRB.
Il est possible de faire en sorte que Pry soit utilisé par défaut, chaque fois qu’IRB est appelé. Si vous souhaitez utiliser PRy en lieu et place d’IRB, voici les quelques lignes de code à ajouter à votre ~/.irbrc
:
begin
require "pry"
Pry.start
exit
rescue LoadError => e
warn "=> Unable to load pry"
end
Pry est donc une alternative très intéressante à IRB et nous offre des fonctionnalités très pratiques pour développer et débugguer. Je vous conseille de jeter un œil au README du projet si vous voulez en apprendre d’avantage.
L’équipe Synbioz.
Libres d’être ensemble.