Interactive Ruby (IRB) is a default gem that comes bundled with Ruby and provides a quick way to experiment with Ruby code within within your console. You’re not limited to tinkering with Ruby within your console, powerful as that is, you can also customize IRB using your own configuration in terms of prompt, colors, and even build your own extensions.
Let’s explore IRB’s features so you can maximize all of what IRB provides to enhance your workflow further.
Setup
To get started, ensure you are using IRB 1.15.0 or higher. Example:
gem list irb
# irb (default: 1.15.2)
💡 Since IRB is a default gem, you can enforce the latest version by updating your Default Gems (as shown in the output above).
Next, you’ll want to create an .irbrc file which, for the uninitiated, ends up in the root of your $HOME directory. Unfortunately, placing all of your Dotfiles in your home directory is messy and difficult to maintain. A saner and more manageable solution is to use the XDG specification and create your configuration here: $HOME/.config/irb/irbrc. To use a XDG configuration, add the following to your Bash profile (or whatever shell you are using) as follows:
export XDG_CONFIG_HOME="$HOME/.config"
Ensure you run exec $SHELL after the above has been applied so your shell picks up the changes and then create a $HOME/.config/irb/irbrc via your favorite editor.
Configuration
With the latest IRB version installed, we can start configuring IRB. The following sections will use my irbrc configuration as a blueprint so you can customize further if desired.
Prompt
The default IRB prompt is rather boring:
irb
# irb(main):001:0>
We can do better! In my irbrc, you’ll notice I use the IRB Kit gem which provides a custom prompt via the following code:
IRB.conf[:PROMPT][:ALCHEMISTS] = {
PROMPT_I: "[#{IRB::Kit.prompt}]> ",
PROMPT_N: "[#{IRB::Kit.prompt}]| ",
PROMPT_C: "[#{IRB::Kit.prompt}]| ",
PROMPT_S: "[#{IRB::Kit.prompt}]%l ",
RETURN: "=> %s\n"
}
IRB.conf[:PROMPT_MODE] = :ALCHEMISTS
This custom prompt checks for Hanami, Rails, and then falls back to Ruby if no framework is found. For complete details, check out the IRB Kit documentation.
Auto Completion
You can use Reline::Face.config via the Reline gem to configure IRB with a color scheme you enjoy instead of using the default. I’m currently using green highlights against a black background with a white scrollbar:
require "irb/completion"
Reline::Face.config :completion_dialog do |config|
config.define :default, foreground: :white, background: :black
config.define :enhanced, foreground: :black, background: :green
config.define :scrollbar, foreground: :bright_white, background: :black
end
Example:
For additional details, see the Reline Face documentation to customize further.
In addition to configuring your IRB auto completion look and feel, you can add type completion by adding the following line to your configuration:
IRB.conf[:COMPLETOR] = :type
Example:
💡 Ensure you install the ReplTypeCompletor gem in order to leverage RBS type signatures. This’ll prevent warnings showing in up in your console stating that the gem isn’t present.
Return Value Omission
In some situations, it can be nice to ignore the return value from an expression. This can be done by ending the expression with a semicolon. Example:
# Will fill your screen with the result.
result = "demo " * 100_000
# Will ignore the return value.
result = "demo " * 100_000;
Aliases
IRB supports aliases for commands you use the most. As per my irbrc configuration, I use the following:
IRB.conf[:COMMAND_ALIASES]
.merge! b: :backtrace,
c: :continue,
e: :edit,
h: :show_cmds,
i: :info,
l: :ls,
n: :next,
m: :measure,
s: :step,
w: :whereami
This allows me to use single letters for reduced typing. These aliases use a similar mapping to the aliases provided by the Debug gem so there is a natural flow when toggling between IRB and Debug when inspecting/debugging Ruby code.
💡 Your custom aliases will show up when showing help documentation (i.e. show_cmds).
Pager
Pagination is enabled, by default, but can be disabled by using irb --no-pager or adding the following to your irbrc configuration:
IRB.conf[:USE_PAGER] = false
History
Enabling a long history allows you to have more information you can reuse or capture for documentation purposes. Here’s what I have in my irbrc file:
IRB.conf[:EVAL_HISTORY] = 1_000
IRB.conf[:HISTORY_FILE] = "#{Dir.home}/.cache/irb/history.log"
Setting EVAL_HISTORY allows for 1,000 entries in your history file before being truncated. Setting HISTORY_FILE to a path of your choice allows you to not clutter your $HOME directory. In my case, an XDG cache path is used to store all of this information. Highly recommend since the XDG specification provides a sane — and clean — way to organize your Dotfiles.
Once you’ve applied your history configuration, you can use the up arrow to cycle through your history or type the history command to page through your history.
Amazing Print
To view your current IRB configuration with or without the Amazing Print gem, run the following within your IRB console:
# Without Amazing Print
IRB.conf.each { |key, value| puts "#{key}=#{value}" }
# With Amazing Print
require "amazing_print"
ap IRB.conf
Inspection
Should you ever need to know the version and configuration you are using, you can jump into an IRB console and use irb_info to print details. For example, at the time of this writing, here’s what I’m using:
Debugging
# IRB
binding.irb
# Debug
binding.break
The advantage of using IRB is there is nothing to require. You can throw a break point in any file, run the code, and immediately start inspecting at the break point. With the Debug gem, you’ll need to require the gem first. All of my projects use the Debug gem so this isn’t a problem but can get cumbersome when working on a project that doesn’t require the Debug gem so you can use IRB’s binding.irb break point as a fallback in those situations. IRB’s debugger isn’t as feature rich as the Debug gem so definitely recommend requiring the Debug gem for a better experience.
IRB has a close integration with the Debug gem which means that if you start with a binding.irb breakpoint — and the Debug gem is required — you can switch to using Debug by typing debug in your console. Otherwise, you’ll end up with the session being abruptly ended.
Definitely check out IRB's documentation for more information.
Source Inspection
IRB provides several commands for importing code into your IRB session. Here’s the breakdown:
-
source: Loads given file into current session and displays each line. -
irb_load: Same as the above but works likeKernel#load. -
irb_require: Same as the above but works likeKernel#require.
Given the above, let’s say we have the following implementation (i.e. demo.rb) relative to the current directory in which you are using IRB:
# frozen_string_literal: true
module Demo
def self.say = puts "HI"
end
We can launch an IRB console and see slightly different behavior:
Source
[3.2.2]> source "demo.rb" [3.2.2]> # frozen_string_literal: true => nil [3.2.2]> [3.2.2]> module Demo [3.2.2]| def self.say = puts "HI" [3.2.2]| end => :say [3.2.2]> => nil
irb_load
[3.2.2]> irb_load "#{Dir.pwd}/demo.rb"
[3.2.2]> # frozen_string_literal: true
=> nil
[3.2.2]>
[3.2.2]> module Demo
[3.2.2]| def self.say = puts "HI"
[3.2.2]| end
=> :say
[3.2.2]>
=> nil
irb_require
[3.2.2]> irb_require "./demo.rb" => true
You’ll notice that each command requires a different path syntax for each file being loaded and only source and irb_load print out each line of the file as the line is being evaluated. In all cases, once the file is parsed, you can immediately make use of the implementation by messaging Demo.say in your console to get expected output.
Show Source
The show_source command helps you view the source code of a method you are debugging. Additionally, you can use the -s option to move up a level and view the superclass source code. Using -ss allows you move up to the grandparent and so forth.
Measurements
IRB makes measuring code convenient via the measure command. Here’s an example of performing a time measurement (default) within an IRB console:
measure
# TIME is added.
(1..1_000_000).sum
# processing time: 0.000102s
# => 500000500000
measure :off
# => nil
With the above, only a single operation is measured but you could choose to leave measure on until you are done performing multiple measurements.
You can also use Stackprof by passing :stackprof as an argument to measure but I’ll leave that up to you to experiment with further.
By default, IRB uses the MEASURE_PROC key to store measurement operations. You can inspect the configuration by running the following in your IRB console (truncated for brevity):
IRB.conf[:MEASURE_PROC]
# {
# TIME: #<Proc:0x000000010b5c5ba0 irb/init.rb:118>,
# STACKPROF: #<Proc:0x000000010b5c5b78 irb/init.rb:128>
# }
Each measurement operation takes five positional parameters:
-
context(IRB::Context): Captures the current IRB session. -
code(String): The code snippet to measure. -
line_number(Integer): The IRB console line number which increments with each new IRB entry. -
*arguments: Additional arguments which are passed on to your measure operation. -
block(Proc): The block of code you want to measure in case typing each line in IRB is too cumbersome.
Given the above, here’s a simple example of measuring Garbage Collection stats using the Refinements gem to calculate the diff between each measurement. You can copy and paste the following in your IRB console:
require "refinements"
using Refinements::Hash
IRB.conf[:MEASURE_PROC][:GC] = lambda do |_context, _code, _line_number, *arguments, &block|
before = GC.stat
block.call
after = GC.stat
puts "Garbage Collection Diff:"
puts before.diff(after)
end
Then, to use the above while still in your IRB console, you can run as follows:
measure :gc
# GC is added.
(1..1_000_000).sum
# Garbage Collection Diff:
# {
# heap_live_slots: [153300, 153370],
# heap_free_slots: [736819, 736749],
# total_allocated_objects: [986171, 986241],
# malloc_increase_bytes: [528848, 532832],
# oldmalloc_increase_bytes: [10737264, 10741248]
# }
# => 500000500000
Again, the above is a simple example but there is plenty of potential for adding multiple custom measurements that meet your needs.
Extensions
IRB Extensions were added in 1.13.0 which allows you to build custom commands and helpers without having to shove everything into your irbrc file. The design of these extensions definitely has flaws:
-
Commands: These don’t adhere to the Command Pattern so you have to use
#executewhich makes it hard to use closures (i.e. procs, lambdas, and/or methods). -
Helpers: These also don’t adhere to the Command Pattern and, worse, use the Singleton pattern which means you can’t inject dependencies for improved testing.
Despite these rough edges, being able to implement extensions allows for sharing and reducing the lines of code in your IRB configuration. If you’d like functionality that leverages the new IRB Extensions, then check out my IRB Kit gem which can enhance your workflow further.
Easter Egg
This is silly — and you have to violate object encapsulation — but you can view the hidden easter egg by running the following:
IRB.__send__ :easter_egg
…which will then produce the following animation:
Conclusion
You’ve learned the core functioinality of IRB as well as how to fold IRB into your personal workflow. Keep an eye on this space because, with so much active development going on, there’s bound to be new features and/or improvements beyond what we’ve learned here.
