Pry, Ruby, and Fun with the Hash Constructor

railsAugust 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: [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.

  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

# 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'
view raw .pryrc hosted with ❤ by GitHub

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

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at [email protected] or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right