Pry, Ruby, and Fun with the Hash Constructor
I recently had a chance to pair with Justin
Searls of
TestDouble, and we got to chatting about
pry and the odd Hash[]
constructor.
Here's a few tips that you might find useful.
The main reason I use pry are:
- Testing Ruby syntax.
- Documentation and source code browsing.
- History support.
cd
into the an object to change the context, andls
to list methods of that object.
Pry Configuration
To install pry with rails, place this in your Gemfile
gem 'pry-rails', :group => :development
Then run bundle install
. Then run rails console
. That gets you the
default pry configuration. At the bottom of this article is my
~/.pryrc
(gist). Create
that file and then run rails c
(short for rails console
).
You'll see this useful reminder of the customizations:
Helpful shortcuts:
h : hist -T 20 Last 20 commands
hg : hist -T 20 -G Up to 20 commands matching expression
hG : hist -G Commands matching expression ever used
hr : hist -r hist -r <command number> to run a command
Samples variables
a_array: [1, 2, 3, 4, 5, 6]
a_hash: { hello: "world", free: "of charge" }
Testing syntax: Hash[]
The Hash[]
method is one of the odder methods in Ruby, and
oh-so-useful if you're doing map, reduce types of operations.
For example, how do you transform all the keys in a hash to be uppercase?
How about if we try this in pry (note, ahash defined in my .pryrc).
[1] (pry) main: 0> a_hash
{
:hello => "world",
:free => "of charge"
}
[2] (pry) main> a_hash.map { |k,v| [k.to_s.upcase, v] }
[
[0] [
[0] "HELLO",
[1] "world"
],
[1] [
[0] "FREE",
[1] "of charge"
]
]
OK, that gives us an Array of tuples.
Then run these two commands. _
is the value of the last expression.
> tmp = _
> Hash[tmp]
{
"HELLO" => "world",
"FREE" => "of charge"
}
Bingo! Now let's dig into this a bit more.
Memoization with Hash
Hash has another unusual constructor useful for memoizing a method's return value when parameters are involved. Justin Weiss wrote a good article explaining it: 4 Simple Memoization Patterns in Ruby (and One Gem).
Here's a quick sample in Pry:
[5] (pry) main: 0> hh = Hash.new { |h, k| h[k] = k * 2 }
{}
[6] (pry) main: 0> hh[2]
4
[7] (pry) main: 0> hh[4]
8
You can even use an array for the key values:
[8] (pry) main: 0> hh = Hash.new { |h, k| h[k] = k[0] * k[1] }
{}
[9] (pry) main: 0> hh[[2,3]]
6
[10] (pry) main: 0> hh[[4,5]]
20
Browsing Documentation and Source
It's super useful to be able to see the documentation for any method
easily, which you can do by the ?
command. Similarly, you can also see
the source, by using $
.
[3] (pry) main> ? Hash[]
From: hash.c (C Method):
Owner: #<Class:Hash>
Visibility: public
Signature: [](*arg1)
Number of lines: 12
Creates a new hash populated with the given objects.
Similar to the literal { _key_ => _value_, ... }. In the first
form, keys and values occur in pairs, so there must be an even number of
arguments.
The second and third form take a single argument which is either an array
of key-value pairs or an object convertible to a hash.
Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
Hash[ [ ["a", 100], ["b", 200] ] ] #=> {"a"=>100, "b"=>200}
Hash["a" => 100, "b" => 200] #=> {"a"=>100, "b"=>200}
Hmmmm…. Hash[]
also takes a plain array. Let's try that:
[16] (pry) main: 0> a_array
[
[0] 1,
[1] 2,
[2] 3,
[3] 4,
[4] 5,
[5] 6
]
[17] (pry) main: 0> Hash[*a_array]
{
1 => 2,
3 => 4,
5 => 6
}
Neat!
Also note that you can see instance methods by prefixing the method name
with #
or using an actual instance, like this:
[19] (pry) main: 0> ? Hash#keys
From: hash.c (C Method):
Owner: Hash
Visibility: public
Signature: keys()
Number of lines: 5
Returns a new array populated with the keys from this hash. See also
Hash#values.
h = { "a" => 100, "b" => 200, "c" => 300, "d" => 400 }
h.keys #=> ["a", "b", "c", "d"]
[20] (pry) main: 0> ? a_hash.keys
Browsing History
History expansion in pry is also nice. As mentioned above, my .pryrc
has 4 history aliases.
h : hist -T 20 Last 20 commands
hg : hist -T 20 -G Up to 20 commands matching expression
hG : hist -G Commands matching expression ever used
hr : hist -r hist -r <command number> to run a command
Let's try those out. It's import to note that the -T
tails results
after doing the grep of the whole history. I.e., the -T 20
strips the
results down to the last 20 that matched.
Show last 20 commands.
[10] (pry) main: 0> h
1: a_hash
2: a_hash.map { |k,v| [key.upcase, v] }
3: a_hash.map { |k,v| [key.to_s.upcase, v] }
4: a_hash.map { |k,v| [k.upcase, v] }
5: a_hash.map { |k,v| [k.to_s.upcase, v] }
6: tmp = _
7: Hash[tmp]
8: ? Hash[]
9: $ Hash[]
Grep all commands for upcase and show last 20 matches.
[11] (pry) main: 0> hg upcase
2: a_hash.map { |k,v| [key.upcase, v] }
3: a_hash.map { |k,v| [key.to_s.upcase, v] }
4: a_hash.map { |k,v| [k.upcase, v] }
5: a_hash.map { |k,v| [k.to_s.upcase, v] }
Grep all commands for upcase and show all. The history of my example is short so below is the same as above. If the history were longer, as it typically will be, then you might get pages of results!
[12] (pry) main: 0> hG upcase
2: a_hash.map { |k,v| [key.upcase, v] }
3: a_hash.map { |k,v| [key.to_s.upcase, v] }
4: a_hash.map { |k,v| [k.upcase, v] }
5: a_hash.map { |k,v| [k.to_s.upcase, v] }
11: hg upcase
cd and ls within Pry
I love to use cd
and ls
in pry
.
cd
changes the context of pry, a bit like the current directory in the shell, except for Ruby objects. And classes are objects too!ls
lists methods available on an object, a bit like listing files in the shell.
[22] (pry) main: 0> cd a_hash.keys
[26] (pry) main / #<Array>: 1> length
2
[27] (pry) main / #<Array>: 1> first
:hello
[28] (pry) main / #<Array>: 1> last
:free
[29] (pry) main / #<Array>: 1> ls
Enumerable#methods:
all? chunk detect each_entry each_with_index entries find flat_map index_by lazy max member? min_by minmax_by one? partition slice_before sum to_table
any? collect_concat each_cons each_slice each_with_object exclude? find_all group_by inject many? max_by min minmax none? original_grep reduce sort_by to_set to_text_table
JSON::Ext::Generator::GeneratorMethods::Array#methods: to_json_without_active_support_encoder
Statsample::VectorShorthands#methods: to_scale to_vector
SimpleCov::ArrayMergeHelper#methods: merge_resultset
Array#methods:
& []= clear cycle drop_while fill frozen? inspect permutation push reverse select slice! third to_gsl_integration_qaws_table to_qaws_table unshift
* abbrev collect dclone each find_index grep join place rassoc reverse! select! sort to to_gsl_vector to_query values_at
+ append collect! deep_dup each_index first hash keep_if pop recode_repeated reverse_each shelljoin sort! to_a to_gslv to_s zip
- as_json combination delete empty? flatten in_groups last prefix reject rindex shift sort_by! to_ary to_gv to_sentence |
<< assoc compact delete_at eql? flatten! in_groups_of length prepend reject! rotate shuffle split to_csv to_h to_xml
<=> at compact! delete_eql extract_options! forty_two include? map pretty_print repeated_combination rotate! shuffle! suffix to_default_s to_json transpose
== blank? concat delete_if fetch fourth index map! pretty_print_cycle repeated_permutation sample size take to_formatted_s to_json_with_active_support_encoder uniq
[] bsearch count drop fifth from insert pack product replace second slice take_while to_gsl_integration_qawo_table to_param uniq!
self.methods: __pry__
locals: _ __ _dir_ _ex_ _file_ _in_ _out_ _pry_
It's worth noting that you can see the modules declaring the methods of the object.
To see more of what pry can do for you, simply type help
at the
command line.
My ~/.pryrc file
# Using these pry gems -- copy to your Gemfile | |
# group :development, :test do | |
# gem 'awesome_print' # pretty print ruby objects | |
# gem 'pry' # Console with powerful introspection capabilities | |
# gem 'pry-byebug' # Integrates pry with byebug | |
# gem 'pry-doc' # Provide MRI Core documentation | |
# gem 'pry-rails' # Causes rails console to open pry. `DISABLE_PRY_RAILS=1 rails c` can still open with IRB | |
# gem 'pry-rescue' # Start a pry session whenever something goes wrong. | |
# gem 'pry-theme' # An easy way to customize Pry colors via theme files | |
# end | |
# === EDITOR === | |
# Use Vi | |
# Pry.editor = 'vi' | |
# Or RubyMine or other | |
Pry.config.editor = proc { |file, line| "rubymine --line #{line} #{file}" } | |
unless defined?(Pry::Prompt) | |
Pry.config.prompt = Pry::NAV_PROMPT | |
end | |
# In case you want to see the project and the env plus the nav | |
# IMHO, too wide. | |
# if Pry::Prompt[:rails] | |
# Pry.config.prompt = Pry::Prompt[:rails] | |
# end | |
# === COLORS === | |
unless ENV['PRY_BW'] && defined?(PryTheme) | |
Pry.color = true | |
Pry.config.theme = "railscasts" | |
Pry.config.prompt = PryRails::RAILS_PROMPT if defined?(PryRails::RAILS_PROMPT) | |
Pry.config.prompt ||= Pry.prompt | |
end | |
# === HISTORY === | |
Pry::Commands.command /^$/, "repeat last command" do | |
_pry_.run_command Pry.history.to_a.last | |
end | |
# == Pry-Nav - Using pry as a debugger == | |
# Shortcut for calling pry_debug | |
def pd | |
Pry.commands.alias_command 't', 'backtrace' | |
Pry.commands.alias_command 's', 'step' | |
Pry.commands.alias_command 'n', 'next' | |
Pry.commands.alias_command 'c', 'continue' | |
Pry.commands.alias_command 'f', 'finish' | |
Pry.commands.alias_command 'ff', 'frame' | |
Pry.commands.alias_command 'u', 'up' | |
Pry.commands.alias_command 'd', 'down' | |
Pry.commands.alias_command 'b', 'break' | |
Pry.commands.alias_command 'w', 'whereami' | |
display_shortcuts | |
end | |
Pry.config.commands.alias_command "h", "hist -T 20", desc: "Last 20 commands" | |
Pry.config.commands.alias_command "hg", "hist -T 20 -G", desc: "Up to 20 commands matching expression" | |
Pry.config.commands.alias_command "hG", "hist -G", desc: "Commands matching expression ever used" | |
Pry.config.commands.alias_command "hr", "hist -r", desc: "hist -r <command number> to run a command" | |
# Fix deprecation warning, so override default for now. | |
# Remember to eventually remove this! | |
# WARNING: the show-doc command is deprecated. It will be removed from future Pry versions. | |
# Please use 'show-source' with the -d (or --doc) switch instead | |
Pry.commands.alias_command '?', 'show-source -d' | |
if defined?(PryByebug) | |
def pry_debug | |
puts "You can also call 'pd' to save typing!" | |
pd | |
end | |
def pp(obj) | |
Pry::ColorPrinter.pp(obj) | |
end | |
end | |
# Use awesome_print (or amazing_print) | |
begin | |
require 'awesome_print' | |
AwesomePrint.pry! | |
rescue LoadError => err | |
begin | |
puts "no awesome_print :( #{err}" | |
puts "trying amazing_print" | |
require 'amazing_print' | |
AmazingPrint.pry! | |
rescue LoadError => err2 | |
puts "no awesome_print :( #{err2}" | |
end | |
end | |
# Hit Enter to repeat last command | |
Pry::Commands.command /^$/, "repeat last command" do | |
pry_instance.run_command Pry.history.to_a.last | |
end | |
before_session_hook = Pry::Hooks.new.add_hook(:before_session, :add_dirs_to_load_path) do | |
# adds the directories /spec and /test directories to the path if they exist and not already included | |
current_dir = `pwd`.chomp | |
dirs_added = %w(spec test presenters lib). | |
map{ |d| "#{current_dir}/#{d}" }. | |
map do |path| | |
if File.exist?(path) && !$:.include?(path) | |
i$: << path | |
path | |
end | |
end.compact | |
puts "Added #{ dirs_added.join(", ") } to load path per hook in ~/.pryrc." unless dirs_added.empty? | |
end | |
before_session_hook.exec_hook(:before_session) | |
def more_help | |
puts "Helpful shortcuts:" | |
puts "hh : hist -T 20 Last 20 commands" | |
puts "hg : hist -T 20 -G Up to 20 commands matching expression" | |
puts "hG : hist -G Commands matching expression ever used" | |
puts "hr : hist -r hist -r <command number> to run a command" | |
puts | |
puts "Samples variables" | |
puts "a_array : [1, 2, 3, 4, 5, 6]" | |
puts "a_hash : { hello: \"world\", free: \"of charge\" }" | |
puts | |
puts "helper : Access Rails helpers" | |
puts "app : Access url_helpers" | |
puts | |
puts "require \"rails_helper\" : To include Factory Girl Syntax" | |
puts "include FactoryGirl::Syntax::Methods : To include Factory Girl Syntax" | |
puts | |
puts "or if you defined one..." | |
puts "require \"pry_helper\"" | |
puts | |
puts "Sidekiq::Queue.new.clear : To clear sidekiq" | |
puts "Sidekiq.redis { |r| puts r.flushall } : Another clear of sidekiq" | |
puts | |
puts "Run `require 'factory_bot'; FactoryBot.find_definitions` for FactoryBot" | |
puts | |
display_shortcuts | |
puts "Debugging Shortcuts" | |
puts | |
puts | |
"" | |
end | |
def display_shortcuts | |
puts "Installed debugging Shortcuts" | |
puts 'w : whereami' | |
puts 's : step' | |
puts 'n : next' | |
puts 'c : continue' | |
puts 'f : finish' | |
puts 'pp(obj) : pretty print object' | |
puts '' | |
puts 'Stack movement' | |
puts 't : backtrace' | |
puts 'ff : frame' | |
puts 'u : up' | |
puts 'd : down' | |
puts 'b : break' | |
puts | |
puts "Introspection" | |
puts '$ : show whole method of current context' | |
puts '$ foo: show definition of foo' | |
puts '? foo: show docs for for' | |
puts | |
puts "Be careful not to use shortcuts for temp vars, like 'u = User.first`" | |
puts "Run `help` to see all the pry commands that would conflict (and lots good info)" | |
puts "Run `more_help to see many helpful tips from the ~/.pryrc`" | |
"" | |
end | |
# Utility global methods for convenience | |
def a_array | |
(1..6).to_a | |
end | |
def a_hash | |
{ hello: "world", free: "of charge" } | |
end | |
def do_time(repetitions = 100, &block) | |
require 'benchmark' | |
Benchmark.bm{|b| b.report{repetitions.times(&block)}} | |
end | |
# by default, set up the debug shortcuts | |
puts "Loaded ~/.pryrc. Setting up debug shortcuts." | |
pd | |
def each_without_puma_config(enumerable) | |
enumerable.filter { |key, _value| key != 'puma.config' } | |
end | |
# https://github.com/pry/pry/issues/2185#issuecomment-945082143 | |
ENV['PAGER'] = ' less --raw-control-chars -F -X' |
Create a file in your home directory called ~/.pryrc
.