Pry, Ruby, and Fun with the Hash Constructor

rubyAugust 17, 2014Dotby Justin Gordon

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:

  1. Testing Ruby syntax.
  2. Documentation and source code browsing.
  3. History support.
  4. cd into the an object to change the context, and ls 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: [1undefined 2undefined 3undefined 4undefined 5undefined 6]
a_hash: { hello: "world"undefined 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"undefined
     :free => "of charge"
}
[2] (pry) main> a_hash.map { |kundefinedv| [k.to_s.upcaseundefined v] }
[
    [0] [
        [0] "HELLO"undefined
        [1] "world"
    ]undefined
    [1] [
        [0] "FREE"undefined
        [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"undefined
     "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 { |hundefined 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 { |hundefined k| h[k] = k[0] * k[1] }
{}
[9] (pry) main: 0> hh[[2undefined3]]
6
[10] (pry) main: 0> hh[[4undefined5]]
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_undefined ... }. In the first
formundefined keys and values occur in pairsundefined 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"undefined 100undefined "b"undefined 200]             #=> {"a"=>100undefined "b"=>200}
   Hash[ [ ["a"undefined 100]undefined ["b"undefined 200] ] ]   #=> {"a"=>100undefined "b"=>200}
   Hash["a" => 100undefined "b" => 200]         #=> {"a"=>100undefined "b"=>200}

Hmmmm…. Hash[] also takes a plain array. Let's try that:

[16] (pry) main: 0> a_array
[
    [0] 1undefined
    [1] 2undefined
    [2] 3undefined
    [3] 4undefined
    [4] 5undefined
    [5] 6
]
[17] (pry) main: 0> Hash[*a_array]
{
    1 => 2undefined
    3 => 4undefined
    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" => 100undefined "b" => 200undefined "c" => 300undefined "d" => 400 }
   h.keys   #=> ["a"undefined "b"undefined "c"undefined "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 { |kundefinedv| [key.upcaseundefined v] }
3: a_hash.map { |kundefinedv| [key.to_s.upcaseundefined v] }
4: a_hash.map { |kundefinedv| [k.upcaseundefined v] }
5: a_hash.map { |kundefinedv| [k.to_s.upcaseundefined 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 { |kundefinedv| [key.upcaseundefined v] }
3: a_hash.map { |kundefinedv| [key.to_s.upcaseundefined v] }
4: a_hash.map { |kundefinedv| [k.upcaseundefined v] }
5: a_hash.map { |kundefinedv| [k.to_s.upcaseundefined 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 { |kundefinedv| [key.upcaseundefined v] }
 3: a_hash.map { |kundefinedv| [key.to_s.upcaseundefined v] }
 4: a_hash.map { |kundefinedv| [k.upcaseundefined v] }
 5: a_hash.map { |kundefinedv| [k.to_s.upcaseundefined v] }
11: hg upcase

cd and ls within Pry

I love to use cd and ls in pry.

  1. cd changes the context of pry, a bit like the current directory in the shell, except for Ruby objects. And classes are objects too!
  2. 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

Create a file in your home directory called ~/.pryrc.

# See https://github.com/justin808/justin808-dotfiles/blob/master/home/.pryrc
# Using these pry gems
# Essentials
# gem "pry"
# gem "pry-rails"
# gem "pry-byebug"
# gem "pry-doc"
# gem "pry-rescue"
# If you like this one:
# gem "pry-state"
# Probably not so necessary
# gem "pry-toys"
# Do not use pry-stack_explorer as it conflicts with pry-byebug
# Pry::Commands.block_command "noconflict", "Rename step to sstep and next to nnext" do
# Pry::Commands.rename_command("nnext", "next")
# Pry::Commands.rename_command("bbreak", "break")
# end
# Pry::Commands.block_command "unnoconflict", "Revert to normal next and break" do
# Pry::Commands.rename_command("next", "nnext")
# Pry::Commands.rename_command("break", "bbreak")
# end
## Useful Collections
def a_array
(1..6).to_a
end
def a_hash
{hello: "world", free: "of charge"}
end
## Benchmarking
# Inspired by <http://stackoverflow.com/questions/123494/whats-your-favourite-irb-trick/123834#123834>.
def do_time(repetitions = 100, &block)
require 'benchmark'
Benchmark.bm{|b| b.report{repetitions.times(&block)}}
end
Pry.config.color = true
# New version of pry uses Pry::Prompt[:nav]
unless defined?(Pry::Prompt)
Pry.config.prompt = Pry::NAV_PROMPT
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"
if defined?(PryByebug)
def pry_debug
puts "You can also call 'pd' to save typing!"
pd
end
# 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 'u', 'up'
Pry.commands.alias_command 'd', 'down'
Pry.commands.alias_command 'b', 'break'
Pry.commands.alias_command 'w', 'whereami'
puts "Installed debugging Shortcuts"
puts 'w : whereami'
puts 's : step'
puts 'n : next'
puts 'c : continue'
puts 'f : finish'
puts 'Stack movement'
puts 't : backtrace'
puts 'ff : frame'
puts 'u : up'
puts 'd : down'
puts 'b : break'
""
end
# def pry_rails
# puts "You can also call 'pr' to save typing!"
# pr
# end
# # Shortcut for calling pry_debug
# def pr
# # Seems these are now loaded automatically in newer Rails
# # require 'factory_bot';
# # FactoryBot.find_definitions
# puts "Added factory support"
# end
# Longer shortcuts that don't conflict
Pry.commands.alias_command 'ff', 'frame'
Pry.commands.alias_command 'ss', 'step'
Pry.commands.alias_command 'nn', 'next'
Pry.commands.alias_command 'cc', 'continue'
Pry.commands.alias_command 'fin', 'finish'
Pry.commands.alias_command 'uu', 'up'
Pry.commands.alias_command 'dd', 'down'
Pry.commands.alias_command 'bb', 'break'
Pry.commands.alias_command 'ww', 'whereami'
end
begin
require 'awesome_print'
# Pry.config.print = proc { |output, value| output.puts value.ai }
AwesomePrint.pry!
rescue LoadError => err
puts "no awesome_print :("
end
my_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
dir = `pwd`.chomp
dirs_added = []
%w(spec test presenters lib).map{ |d| "#{dir}/#{d}" }.each do |p|
if File.exist?(p) && !$:.include?(p)
$: << p
dirs_added << p
end
end
puts "Added #{ dirs_added.join(", ") } to load path in ~/.pryrc." if dirs_added.present?
end
# Hit Enter to repeat last command
Pry::Commands.command /^$/, "repeat last command" do
_pry_.run_command Pry.history.to_a.last
end
my_hook.exec_hook(:before_session)
puts "Loaded ~/.pryrc"
puts
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 "Debugging Shortcuts"
puts 'ss : step'
puts 'nn : next'
puts 'cc : continue'
puts 'fin : finish'
puts 'uu : up'
puts 'dd : down'
puts 'bb : break'
puts 'w : whereami'
puts 'ff : frame'
puts 't : backtrace'
puts '$ : show whole method of context'
puts
puts "Run 'pry_debug' or 'pd' to display shorter debug shortcuts"
# puts "Run 'pry_rails' or 'pr' run rails helpers"
puts "Run `require 'factory_girl'; FactoryBot.find_definitions` for FactoryBot"
""
end
puts "Run 'more_help' to see tips"
view raw .pryrc hosted with ❤ by GitHub

Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right