In Progress
Unit 1, Lesson 1
In Progress

Awesome Print

Video transcript & code

One of the most powerful aspects of working in a dynamic language like Ruby is the ability to use a Read-Eval-Print-Loop, or REPL, to explore and develop interactively. But a REPL is only as good as the tools available to print out data in a useful way. Today I thought we might take a look at a tool for supercharging Ruby's ability to represent objects as text.

Let's open up IRB, Ruby's built-in REPL. We'll start by looking at some JSON data from a web app, so we'll require the json and open-uri standard libraries.

$ irb
irb(main):001:0> require "open-uri"
=> true
irb(main):015:0> require "json"
=> true

Then we'll read in some JSON data and parse it.

irb(main):016:0> tasks = JSON.parse(open("http://localhost:3000/tasks.json").read)
=> [{"id"=>1, "title"=>"Pick a topic", "completed"=>false, "url"=>"http://localhost:3000/tasks/1.json"},
 {"id"=>2, "title"=>"Compose script", "completed"=>false, "url"=>"http://localhost:3000/tasks/2.json"},
{"id"=>3, "title"=>"Record screen", "completed"=>false, "url"=>"http://localhost:3000/tasks/3.json"}, {"
id"=>4, "title"=>"Record VO", "completed"=>false, "url"=>"http://localhost:3000/tasks/4.json"}, {"id"=>5
, "title"=>"Produce video", "completed"=>false, "url"=>"http://localhost:3000/tasks/5.json"}, {"id"=>6,
"title"=>"Export video", "completed"=>false, "url"=>"http://localhost:3000/tasks/6.json"}, {"id"=>7, "ti
tle"=>"Post episode", "completed"=>false, "url"=>"http://localhost:3000/tasks/7.json"}]

Next we'll require the pp library, for prettier printing of data.

irb(main):018:0> require "pp"
=> true

We first met the pp library way back in episode #115.

Let's check out the JSON data using pp.

irb(main):025:0> pp tasks
[{"id"=>1,
  "title"=>"Pick a topic",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/1.json"},
 {"id"=>2,
  "title"=>"Compose script",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/2.json"},
 {"id"=>3,
  "title"=>"Record screen",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/3.json"},
 {"id"=>4,
  "title"=>"Record VO",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/4.json"},
 {"id"=>5,
  "title"=>"Produce video",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/5.json"},
 {"id"=>6,
  "title"=>"Export video",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/6.json"},
 {"id"=>7,
  "title"=>"Post episode",
  "completed"=>false,
  "url"=>"http://localhost:3000/tasks/7.json"}]
=> [{"id"=>1, "title"=>"Pick a topic", "completed"=>false, "url"=>"http://localhost:3000/tasks/1.json"},
 {"id"=>2, "title"=>"Compose script", "completed"=>false, "url"=>"http://localhost:3000/tasks/2.json"},
{"id"=>3, "title"=>"Record screen", "completed"=>false, "url"=>"http://localhost:3000/tasks/3.json"}, {"
id"=>4, "title"=>"Record VO", "completed"=>false, "url"=>"http://localhost:3000/tasks/4.json"}, {"id"=>5
, "title"=>"Produce video", "completed"=>false, "url"=>"http://localhost:3000/tasks/5.json"}, {"id"=>6,
"title"=>"Export video", "completed"=>false, "url"=>"http://localhost:3000/tasks/6.json"}, {"id"=>7, "ti
tle"=>"Post episode", "completed"=>false, "url"=>"http://localhost:3000/tasks/7.json"}]

This isn't bad, as formatted data goes. It's broken across multiple lines, with indentation to show the hash keys and values belong to hash objects.

But now, let's require awesome_print.

irb(main):021:0> require "awesome_print"
=> true

And then let's print out the same data, this time using the ap helper instead of pp.

irb(main):030:0> ap tasks
[
    [0] {
               "id" => 1,
            "title" => "Pick a topic",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/1.json"
    },
    [1] {
               "id" => 2,
            "title" => "Compose script",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/2.json"
    },
    [2] {
               "id" => 3,
            "title" => "Record screen",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/3.json"
    },
    [3] {
               "id" => 4,
            "title" => "Record VO",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/4.json"
    },
    [4] {
               "id" => 5,
            "title" => "Produce video",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/5.json"
    },
    [5] {
               "id" => 6,
            "title" => "Export video",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/6.json"
    },
    [6] {
               "id" => 7,
            "title" => "Post episode",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/7.json"
    }
]
=> nil

This is quite an upgrade on the pp output. The first thing we notice is that there is now syntax highlighting to better identify different types of literals. The hash keys and values aren't just indented; they are aligned on the hashrocket symbols for better readability. The array indexes are numbered, which would make it easy for us to pick out an individual record if we needed to.

One problem that we sometimes run into with dumping data in the REPL is that when the dataset is very large, it can fill up the screen and just keep scrolling. One of awesome_print's features is the ability to limit the number of data elements to be shown, using an optional keyword argument.

irb(main):034:0> ap tasks, limit: 3
[
    [0] {
               "id" => 1,
        "title" => "Pick a topic" .. "completed" => false,
              "url" => "http://localhost:3000/tasks/1.json"
    },
    [1] .. [5],
    [6] {
               "id" => 7,
        "title" => "Post episode" .. "completed" => false,
              "url" => "http://localhost:3000/tasks/7.json"
    }
]

Here we can see that awesome_print has shown just the first and last items, with an indication of how many were elided in the middle. Not only that, but we can see that the limit is recursive. Only three of the four hash keys are shown inside each record.

It's also possible to set up a global limit; check out the awesome_print documentation for how to do that.

The awesome_print view of our data is so useful, we'd like it to be our default whenever we evaluate data in IRB. Fortunately, this is easy to set up. All we have to do is call awesome_print.irb!

irb(main):031:0> AwesomePrint.irb!

Now watch as I enter tasks all by itself, without ap in front of it.

irb(main):032:0> tasks
[
    [0] {
               "id" => 1,
            "title" => "Pick a topic",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/1.json"
    },
    [1] {
               "id" => 2,
            "title" => "Compose script",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/2.json"
    },
    [2] {
               "id" => 3,
            "title" => "Record screen",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/3.json"
    },
    [3] {
               "id" => 4,
            "title" => "Record VO",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/4.json"
    },
    [4] {
               "id" => 5,
            "title" => "Produce video",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/5.json"
    },
    [5] {
               "id" => 6,
            "title" => "Export video",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/6.json"
    },
    [6] {
               "id" => 7,
            "title" => "Post episode",
        "completed" => false,
              "url" => "http://localhost:3000/tasks/7.json"
    }
]

As we can see, awesome_print is now integrated into IRB, and has become the default echo formatter.

Just as the name promised, this is pretty awesome. But we've only seen the beginning of awesomeprint's capabilities.

One surprising trick up awesome_print's sleeve has to do with printing lists of methods. Let's create a string, and then tell awesome_print to print out its methods.

irb(main):020:0> s = "hello"
irb(main):019:0> ap s.methods
[
    [  0]                          !()                 String (BasicObject)
    [  1]                         !=(arg1)             String (BasicObject)
    [  2]                         !~(arg1)             String (Kernel)
    [  3]                          %(arg1)             String
    [  4]                          *(arg1)             String
    [  5]                          +(arg1)             String
    [  6]                          <(arg1)             String (Comparable)
    [  7]                         <<(arg1)             String
...

Instead of seeing the list of plain symbols we might expect, we see a list of methods, complete with argument information and info on what class or module defines the method.

So far we've seen awesome_print formatting basic Ruby data types. But now, let's require the nokogiri library and read in some HTML from our web app.

irb(main):035:0> require "nokogiri"
true
irb(main):036:0> doc = Nokogiri::HTML(open("http://localhost:3000").read)

Then we'll require the awesome_print/ext/nokogiri library.

irb(main):042:0> require "awesome_print/ext/nokogiri"
true

By the way, this step is only necessary because we loaded awesome_print before we loaded nokogiri. If nokogiri had already been loaded when we loaded awesome_print, it would have detected it and auto-loaded the appropriate extension.

Next up, let's get a list of all the a tags on the page.

irb(main):043:0> doc.css("a").to_a
[
    [ 0] <a href="/tasks/1">Show</a>,
    [ 1] <a href="/tasks/1/edit">Edit</a>,
    [ 2] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/1">Destroy</a>
,
    [ 3] <a href="/tasks/2">Show</a>,
    [ 4] <a href="/tasks/2/edit">Edit</a>,
    [ 5] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/2">Destroy</a>
,
    [ 6] <a href="/tasks/3">Show</a>,
    [ 7] <a href="/tasks/3/edit">Edit</a>,
    [ 8] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/3">Destroy</a>
,
    [ 9] <a href="/tasks/4">Show</a>,
    [10] <a href="/tasks/4/edit">Edit</a>,
    [11] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/4">Destroy</a>
,
    [12] <a href="/tasks/5">Show</a>,
    [13] <a href="/tasks/5/edit">Edit</a>,
    [14] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/5">Destroy</a>
,
    [15] <a href="/tasks/6">Show</a>,
    [16] <a href="/tasks/6/edit">Edit</a>,
    [17] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/6">Destroy</a>
,
    [18] <a href="/tasks/7">Show</a>,
    [19] <a href="/tasks/7/edit">Edit</a>,
    [20] <a data-confirm="Are you sure?" rel="nofollow" data-method="delete" href="/tasks/7">Destroy</a>
,
    [21] <a href="/tasks/new">New Task</a>
]

Not only can we see the neat awesome_print array formatting in this output. We also see that it has provided syntax highlighting for the Nokogiri element nodes!

So far we've only scratched the surface of awesome_print's capabilities. In a future episode we'll see how it can be particularly helpful when developing Rails applications. But what we've seen today is more than enough to improve our REPL sessions considerably. Happy hacking!

Responses