pry notes

June 15th, 2018 - Bonn

Ruby console / pry tricks

History

hist (history)
hist --all (-a)
hist -T (--tail) 20
hist -G (--grep) (REGEX)
hist -r (--replay) 14..16
hist -n (--no-numbers)

# You could define aliases like these ones
# "hh", "hist -a -T30"
# "hn", "hist -a -T30 -n"
# "hg", "hist -a -G"
# "hr", "hist -a -r"
#
# To support CTRL + R
# https://github.com/pry/pry/wiki/FAQ#how-can-i-get-readline-support-ctrlr-etc-for-pry-in-osx
#
# .cat #{Pry.config.history.file}
# cat ~/.pry_history
# Pry.history.to_a

# You can load the previous history to avoid the --all flag and use CTRL + R and the up key
# Pry.load_history # same as Pry.history.load

General tips

# Shell
.open #{Shop.first.image.path}
.ls #{output_file_path_for_upload}

# Name conflicts
# ls = 1
# => SyntaxError: syntax error, unexpected '='
# ;ls =1

# skip dump
(1..100).to_a;

# latest output
_

Copy & Paste

Quick Trick : Make the font smaller

with pbcopy, pbpaste for mac

foo = '123'
`echo #{foo} | pbcopy`

foo = {a: "test\n", b:2}
`echo #{foo} | pbcopy` # does not work
`echo #{Shellwords.escape(foo)} | pbcopy`
# better
IO.popen('pbcopy', 'w') { |f| f << foo }

copied_foo = eval(`pbpaste`)

with the pry-clipboard gem

# multiplatform
Clipboard.copy Array.methods
foo = Clipboard.paste
copy-result
copy-history
# Aliases
"cr", "copy-result"
"cc", "copy-history -T -2 -l" # copy input und output, at least in my case

Extra Gems

They can be useful, but some of them have not been updated in a long time, may not work with your rails version or be too unstable.

Output

array_sample = (1..100).to_a
hash_sample = (1..200).to_a.in_groups_of(2).to_h
# to play with more realistic examples, using the countries gem:
array_sample = ISO3166::Country.all_names_with_codes
hash_sample = ISO3166::Country.search('de')

print array_sample;
puts #foo.to_s + newline after each argument
p # puts with foo.inspect
pp # pretty print - indentation

j/jj # json / pretty json

# you can get a simpler kind of output even without changing the inspector
array_sample.inspect

Pry.config.pager = false
# IMPORTANT: Use Pry.xxx in your .pryrc, use _pry_.xxx in your repl session
_pry_.config.pager = false

# pager is Less by default, you can change it
# https://github.com/pry/pry/wiki/FAQ#how-do-i-use-other-pager-than-less
#
# Some Less shortcuts
#  h  show help
#  q  quit
#  G  end of file
#  g  start of file
#  * mac: fn left right top down
#  /  search forward
#  ?  search backwards
#  n  next match (forwards/backwards)
#  N  previous match (forwards/backwards)

Tabular Data

poor man’s table

# First: ways of string interpolation, and what they ruby style guide says about it (https://github.com/bbatsov/ruby-style-guide)
# sprintf (alias: format) better than String#%
# sprinft saves to variable, printf prints

format('%-5d %-5d', 20, 10)
format('%<first>-5d %<second>-5d', first: 20, second: 10)
# => "20    10   " (5 chars in total for every part)


def table_example
  printf("%-10s %-20s\n", 'id', 'text')
  Question.limit(4).pluck(:id, :text).each do |attrs|
    printf("%-10s %-20s\n", *attrs)
  end
  nil
end

# =>
id         text
1          Iste sint a cumque c
3          Omnis animi ea archi
4          Facere ut nihil nisi
9          Eum et incidunt veri

With Hirb

def _table(*args)
  print Hirb::Helpers::AutoTable.render(*args)
end

_table Question.limit(5), fields: %i(id questionnaire_id text)

# =>
+----+------------------+-------------------------------------+
| id | questionnaire_id | text                                |
+----+------------------+-------------------------------------+
| 1  | 6                | Iste sint a cumque consectetur.     |
| 2  | 7                | Omnis animi ea architecto reicie    |
| 3  | 8                | Facere ut nihil nisi eaque praes... |
| 4  | 9                | Eum et incidunt veritatis ut.       |
| 5  | 10               | Non voluptatem praesentium sint ... |
+----+------------------+-------------------------------------+

_table [['foo', 'bar', 'baz'], [1,2,3], [4,5,6]]
_table [{foo: '1', bar: '2'}, {foo: '3', bar: '4'}]
_table (1..3).map {|i| i.days.from_now }, :fields=>[:to_s, :year, :month, :day]

Inspectors

list-inspectors
change-inspector # default, simple, clipped
# custom: awesome, hirb, special_cases....

* awesome_print
ap User.all

Custom Inspectors

from https://github.com/pry/pry/blob/v0.10.4/lib/pry/commands/change_inspector.rb

if inspector_map.key?(inspector)
  _pry_.print = inspector_map[inspector][:value]
..

from https://github.com/pry/pry/blob/v0.10.4/lib/pry/inspector.rb

class Pry::Inspector
  MAP = {
    ...
    "simple" => {
      value: Pry::SIMPLE_PRINT,
      description: <<-DESCRIPTION.each_line.map(&:lstrip)
        A simple inspector that uses #puts and #inspect when printing an
        object. It has no pager, color, or pretty_inspect support.
      DESCRIPTION
    },

from https://github.com/pry/pry/blob/v0.10.4/lib/pry.rb

  SIMPLE_PRINT = proc do |output, value|
    begin
      output.puts value.inspect
    rescue RescuableException
      output.puts "unknown"
    end
  end

We can extend them with our own, to add awesome_print or hirb for example (see Config File) or special logic:

# Like default, but if the value is a User, it highlights the email and role

Pry::Inspector::MAP['default_with_special_cases'] = {
  description: 'provides extra info for some models',
  value: proc do |output, value, _pry_|
    case value
    when User
      puts "\e[34m #{ value.role } #{ value.email } \e[0m\n -------- \n"
    end
    Pry::DEFAULT_PRINT.call(output, value, _pry_)
  end
}

Prompts

list-prompts
change-prompt # simple, nav, none

# custom: rails_app, memory, ...

Custom Prompts

Analogous to the inspectors

the default prompt has a variable to configure the project name

Pry.config.prompt_name = File.basename(Dir.pwd)

Pry.config.prompt_name = File.basename(Dir.pwd)

Pry::Prompt::MAP['rails_app'] = {
  description: 'prompt with app name and environment',
  value: proc do |obj, nest_level, _pry_|
    env_warning = case Rails.env
      when 'production' then " \e[41mproduction\e[0m "
      when 'staging' then " \e[42mstaging\e[0m "
    end
    "#{_pry_.config.prompt_name}#{env_warning}[pry@#{obj}]> "
  end
}

# Set rails_app as default prompt
Pry.config.prompt = Pry::Prompt::MAP['rails_app'][:value]

# Other example usign pry text helpers: http://phansch.net/2017/02/12/a-better-pry-prompt-for-rails-console/

Pry::Prompt::MAP['memory'] = {
  description: 'shows PID and memory',
  value: proc do |obj, nest_level, _pry_|
  pid, size = `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)
  "PID:#{pid} MEM:#{size}KB(#{helper.number_to_human_size(size * 1000)}) [pry@#{obj}] > "
  end
}

Colors

adapted from http://stackoverflow.com/questions/1489183/colorized-ruby-output

module PryColorize
  module_function

  def black(str);          "\e[30m#{str}\e[0m" end
  def red(str);            "\e[31m#{str}\e[0m" end
  def green(str);          "\e[32m#{str}\e[0m" end
  def brown(str);          "\e[33m#{str}\e[0m" end
  def blue(str);           "\e[34m#{str}\e[0m" end
  def magenta(str);        "\e[35m#{str}\e[0m" end
  def cyan(str);           "\e[36m#{str}\e[0m" end
  def gray(str);           "\e[37m#{str}\e[0m" end

  def bg_black(str);       "\e[40m#{str}\e[0m" end
  def bg_red(str);         "\e[41m#{str}\e[0m" end
  def bg_green(str);       "\e[42m#{str}\e[0m" end
  def bg_brown(str);       "\e[43m#{str}\e[0m" end
  def bg_blue(str);        "\e[44m#{str}\e[0m" end
  def bg_magenta(str);     "\e[45m#{str}\e[0m" end
  def bg_cyan(str);        "\e[46m#{str}\e[0m" end
  def bg_gray(str);        "\e[47m#{str}\e[0m" end

  def bold(str);           "\e[1m#{str}\e[22m" end
  def italic(str);         "\e[3m#{str}\e[23m" end
  def underline(str);      "\e[4m#{str}\e[24m" end
  def blink(str);          "\e[5m#{str}\e[25m" end
  def reverse_color(str);  "\e[7m#{str}\e[27m" end

  def list
    methods = singleton_methods(false) - [:list]
    methods.each { |m| puts send(m, format('%-20s', m)) }
  end
end

There are also some pry helpers (http://www.rubydoc.info/github/pry/pry/Pry/Helpers/Text)

Pry::Helpers::Text.black
Pry::Helpers::Text.red
Pry::Helpers::Text.green
Pry::Helpers::Text.yellow
Pry::Helpers::Text.blue
Pry::Helpers::Text.purple
Pry::Helpers::Text.magenta
Pry::Helpers::Text.cyan
Pry::Helpers::Text.white
--
Pry::Helpers::Text.bold
Pry::Helpers::Text.indent

Cool use: giving a warning to other devs while initializing:

# config/initializers/meta_request_warning.rb
# the meta-request gem conflicts with the activerecord-postgis-adapter
# doing a Shop.first.update_attributes({}) on the console leads to a SystemStackError
if defined? MetaRequest
  logger = Logger.new(STDOUT)
  logger.warn "\e[31mThe meta-request gem can cause a SystemStackError: stack level too deep when saving a shop record. See https://github.com/rgeo/activerecord-postgis-adapter/issues/81 \e[0m"
end
bundle exec rails c
The meta-request gem can cause a SystemStackError: stack level
too deep when saving a shop record.
See
  https://github.com/rgeo/activerecord-postgis-adapter/issues/81
Loading development environment (Rails 4.2.6)

Emoji! 🦄

Images

def see(image_path)
  name = inline_base64(File.basename(image_path))
  image = inline_base64(File.read(image_path))
  puts "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n"
end

def inline_base64(path)
  Base64.encode64(path).gsub("\n", "")
end

Exploring/Debugging/Editing

binding.pry

whereami # @ -n
show-source
ls
cd # cd..
show-source # @  whereami
show-source Foo -a # Found 2 candidates for `Foo` definition: ...
find-method api_user?
find-method location Uberall::Client
find-method -c business_id Uberall::Client # will grep the source code

Foo._tab_autocomplete
edit -p Foo # $EDITOR or Pry.config.editor # reload

Exiting / Escaping the loop

binding.pry if x
binding.pry if $x
binding.pry unless @once; @once = true

exit
!!! (exit-program)
disable-pry
* enable-pry

docs

# gem 'pry-doc'

? Array#reduce (alias show-doc)
? Shop.belongs_to

rails info

# gem 'pry-rails'

show-middleware
show-model
show-models
show-routes

byebug

# gem 'pry-byebug'
# break, step, next, finish, continue
# backtrace, up, down, frame
class A
  def foo
    B.new.bar
  end
end

class B
  def bar
    binding.pry
    C.new.baz
  end
end

class C
  def baz
    1+1
  end
end

A.new.foo

more

#  Calling binding.pry within a method of a class inheriting from SimpleDelegator
::Kernel.binding.pry

# looking into a gem
bundle show devise
bundle open devise
gem pristine devise # undo any edits (try edit -p also to avoid this problem)
# http://guides.rubygems.org/command-reference/#gem-pristine

# using regular ruby
method(:foo).source_location
Movie.instance_method(:average_stars).source_location
Movie.instance_method(:average_stars).super_method.source_location # find where super is pointing to
caller # caller(1,4)

notifications / sounds

# gem 'terminal-notifier'

def ping_when_finished(&block)
  block.call if block_given?
  `say fertig`
  `terminal-notifier -message "finished" -sound default -timeout 5`
end

waiting for iterations to finish

# gem 'tqdm'

module Enumerable
  def measure(&block)
    Benchmark.measure do
      ActiveRecord::Base.logger.silence do
        self.tqdm.each(&block)
      end
    end
  end
end

Foreman + pry / Remote Debugging

gem 'pry-remote'  # latest comit August 2014

binding.remote_pry
#=> [pry-remote] Waiting for client on drb://localhost:9876  # at least in theory

# from other terminal
bundle exec pry-remote

binding.remote_pry('127.0.0.1', 9888)
bundle exec pry-remote -s 127.0.0.1 -p 9888 -w # -w = wait

# Address already in use - bind(2) for "127.0.0.1" port 9876
# restart or kill -9 $(lsof -ti tcp:9876)

Using byebyebug

# config/initializers/byebug.rb
if Rails.env.development?
  require 'byebug/core'
  Byebug.wait_connection = true
  Byebug.start_server 'localhost', ENV.fetch("BYEBUG_SERVER_PORT", 8989).to_i
end

# somewhere
byebug

bundle exec byebug -R localhost:8989
  • With better errors you could force an exception to get a little insight.
  • With web-console you get a console after rendering the view

Config - .pryrc

  • Isolate from project / other devs
    • load '.pryrc_custom' if File.exist?('.pryrc_custom')
    • pry_manual_gem_require
  • Cheatsheet
# Method to try to load gems outside of the gemfile context
#   pry_manual_gem_require('my_gem')
#   pry_manual_gem_require('my_gem', 'other_dependency')
#
# Check the corresponding .gemspec to add more explicit dependencies if needed
def pry_manual_gem_require(*gem_names)
  gem_names.flatten.each do |gem_name|
    path = Bundler.with_clean_env { %x[gem which #{gem_name} 2>/dev/null] }.strip
    next if path.blank?
    $LOAD_PATH.unshift(File.dirname(path))
  end
  gem_names.flatten.each { |gem_name| require gem_name }
  yield if block_given?
rescue LoadError
end

pry_manual_gem_require(%w(pry-clipboard clipboard))

### COLORS #####################################################################
# See all colors with PryColorize.list
module PryColorize
  module_function

  def black(str);          "\e[30m#{str}\e[0m" end
  def red(str);            "\e[31m#{str}\e[0m" end
  def green(str);          "\e[32m#{str}\e[0m" end
  def brown(str);          "\e[33m#{str}\e[0m" end
  def blue(str);           "\e[34m#{str}\e[0m" end
  def magenta(str);        "\e[35m#{str}\e[0m" end
  def cyan(str);           "\e[36m#{str}\e[0m" end
  def gray(str);           "\e[37m#{str}\e[0m" end

  def bg_black(str);       "\e[40m#{str}\e[0m" end
  def bg_red(str);         "\e[41m#{str}\e[0m" end
  def bg_green(str);       "\e[42m#{str}\e[0m" end
  def bg_brown(str);       "\e[43m#{str}\e[0m" end
  def bg_blue(str);        "\e[44m#{str}\e[0m" end
  def bg_magenta(str);     "\e[45m#{str}\e[0m" end
  def bg_cyan(str);        "\e[46m#{str}\e[0m" end
  def bg_gray(str);        "\e[47m#{str}\e[0m" end

  def bold(str);           "\e[1m#{str}\e[22m" end
  def italic(str);         "\e[3m#{str}\e[23m" end
  def underline(str);      "\e[4m#{str}\e[24m" end
  def blink(str);          "\e[5m#{str}\e[25m" end
  def reverse_color(str);  "\e[7m#{str}\e[27m" end

  def list
    methods = singleton_methods(false) - [:list]
    methods.each { |m| puts send(m, format('%-20s', m)) }
  end
end

### INSPECTORS #################################################################
pry_manual_gem_require('awesome_print') do
  Pry::Inspector::MAP['awesome'] = {
    value: proc { |output, value| output.puts value.ai },
    description: 'Awesome Print'
  }
end

pry_manual_gem_require('hirb') do
  Hirb.enable
  Pry::Inspector::MAP['hirb'] = {
    value: proc do |*args|
      Hirb::View.view_or_page_output(args[1]) || Pry::DEFAULT_PRINT.call(*args)
    end,
    description: 'Hirb (tabular data)'
  }
  # _table [['foo', 'bar', 'baz'], [1,2,3], [4,5,6]]
  # _table [{foo: '1', bar: '2'}, {foo: '3', bar: '4'}]
  # _table (1..3).map {|i| i.days.from_now }, :fields=>[:to_s, :year, :month, :day]
  def _table(*args)
    print Hirb::Helpers::AutoTable.render(*args)
  end
end

# Example custom Inspector
# Pry::Inspector::MAP['default_with_special_cases'] = {
#   description: 'provides extra info for some models',
#   value: proc do |output, value, pry_|
#     case value
#     when User
#       puts "\e[34m #{value.role.name} #{value.email} \e[0m"
#     end
#     Pry::DEFAULT_PRINT.call(output, value, pry_)
#   end
# }

### PROMPTS ####################################################################
Pry.config.prompt_name = File.basename(Dir.pwd)

Pry::Prompt::MAP['rails_app'] = {
  description: 'prompt with app name and environment',
  value: proc do |obj, _, pry_|
    env_warning =
      case Rails.env
      when 'production' then " #{PryColorize.bg_red('production')} "
      when 'staging' then " #{PryColorize.bg_green('staging')} "
      end
    "#{pry_.config.prompt_name}#{env_warning}[pry@#{obj}]> "
  end
}

Pry::Prompt::MAP['memory'] = {
  description: 'shows PID and memory',
  value: proc do |obj, _, _|
    pid, size = `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)
    "PID:#{pid} MEM:#{size}KB(#{helper.number_to_human_size(size * 1000)}) [pry@#{obj}] > "
  end
}

# Set rails_app as default prompt
Pry.config.prompt = Pry::Prompt::MAP['rails_app'][:value]

### COMMANDS AND ALIASES #######################################################
Pry::Commands.block_command 'hl', 'Pry.history.load' do
  Pry.history.load
end

Pry.commands.alias_command 'hh', 'hist -a -T20'
Pry.commands.alias_command 'hn', 'hist -a -T20 -n'
Pry.commands.alias_command 'hg', 'hist -a -G'
Pry.commands.alias_command 'hr', 'hist -a -r'

Pry::Commands.block_command('cheat', 'Display Cheatsheet') do
  puts 'Shortcuts:'
  puts '@ : whereami'
  puts '? : show-doc'
  puts 'hl : Pry.history.load         load whole history'
  puts 'hh : hist -a -T20             Last 20 commands'
  puts 'hn : hist -a -T20 -n          Last 20 command no numbers'
  puts 'hg : hist -a -G               Commands matching expression'
  puts 'hr : hist -a -r               hist -r <command number> to run a command'
  puts 'help alias                    See all the aliases available'
end

### PROJECT HISTORY FILE #######################################################
Pry.config.history.file = Rails.root.join('.pry_history')

### CUSTOM #####################################################################
custom_config_path = Rails.root.join('.pryrc_custom')
load custom_config_path if File.exist?(custom_config_path)

Other

  • byebug Rails default debugger

  • binding.irb in ruby 2.4


Edits:

  • In my case I need the to require rb-readline to load the history:
# .pryrc_custom

pry_manual_gem_require('rb-readline')
  • In order to highlight the environment in production/staging we need to load pry, you might not want to do that and use .irbrc instead:
# .irbrc

if defined?(Rails) && Rails.env
  reset = "\e[0m"
  color = case Rails.env
          when 'production' then "\e[41m" # red background
          when 'staging' then "\e[42m" # green background
          end

  app_name_env = Rails.application.class.parent_name.underscore
  app_name_env << "#{reset}:#{color}#{Rails.env}#{reset}"

  IRB.conf[:PROMPT][:RAILS_ENV] = {
    PROMPT_I: "%N(#{app_name_env}):%03n:%i> ",
    PROMPT_N: "%N(#{app_name_env}):%03n:%i> ",
    PROMPT_S: "%N(#{app_name_env}):%03n:%i%l ",
    PROMPT_C: "%N(#{app_name_env}):%03n:%i* ",
    RETURN: "=> %s\n"
  }
  IRB.conf[:PROMPT_MODE] = :RAILS_ENV
end