RubyGuides
Share this post!

Code Reading Adventures in The Land of Ruby

I always recommend code reading, not only because it’s good for improving your skills, but also because it’s like an adventure!

You never know what you are going to find ๐Ÿ™‚

So this time I wanted to look into the awesome_print gem.

Awesome print is a nice gem that formats your output in irb & pry to make it more readable.

For example, this is what displaying a hash with awesome_print looks like:

ruby awesome print

But how does this work?

โ€œTruth can only be found in one place: the code.โ€ โ€• Robert C. Martin

Ok then! Let’s take a look at the source code to find out!

Printing Awesomely

I like to start a code reading session with a quick overview of the project structure (files & folders), then I like to ask a question to focus my exploration going forward.

So the first question I came up with is:

How does awesome_print change the output of pry?

Now I put my detective hat on & make a hypothesis:

“This gem could be replacing $stdout so that it can capture the output of pry & then make it pretty.”

The problem is that this will not let you customize your pretty output, like pretty_print (from Ruby’s Standard Library) does.

Also it would not be a very flexible way to do things since we wouldn’t know what kind of object we are working with.

So the next thing I looked into is how this gem is loaded.

This will give us an entry point into the code.

Loading Awesome Print

To load awesome_print on pry you have to do this:

To find this pry! method I used the “search in directory” feature in Atom.

Here’s the method:

Well it seems like Pry allows you to modify its output by setting the value of this print variable. So this answers our initial question ๐Ÿ™‚

Want to learn more about this “proc” thing? Read “The Ultimate Guide to Blocks, Procs & Lambdas“. This is a sample chapter of my Ruby Deep Dive book.

But what is this ai method?

It’s a method defined on the Kernel module:

Since all objects include Kernel by default they will have this method available on them.

Now let’s dig a little deeper & see how this Inspector class works.

The Inspector Class

Right away after opening inspector.rb we can see a HUGE options hash inside the initialize method.

Here’s part of it:

After that we can find this code:

So this sets up two more objects which seem to handle the formatting itself & indentation of the code.

But what’s with this Thread.current thing?

Well this gives you access to the current thread. Even if you are not using threads in your application you will have one, the “main” thread.

This AP is just a constant which is defined at the top of inspector.rb:

So what’s happening here?

Awesome print is using Thread.current & the __awesome_print__ key to save some data that is only available on the current thread.

This is used to avoid problems with multi-threading.

Awesome Formatting

Let’s take a look at the output formatting code, which happens inside the AwesomePrint::Formatter class.

The way this works is that the inspector (the object created by the ai method) will call the format method.

Then this format method on the Formatter class will find the best way deal with this type of object & call another method using a bit of metaprogramming.

Here’s the method:

To understand this Formatter class we also need to take a look at the cast method:

The CORE constant is an array of symbols representing core Ruby classes & :self is just a symbol used to mean “not found” (just in this example, not in Ruby in general).

What happens is that if the object being formatted is in the “core class” list then it will get a specialized formatter.

Otherwise it will get a generic formatter.

These specialized formatters are defined under the /lib/awesome_print/formatters/ directory & include things like Array, Hash & Class.

For example, here’s the format method for classes:

Summary

You have learned about the Awesome Print gem, which lets you display objects like arrays & hashes in a nice way. You also learned how you can make a method available on every Ruby object by defining it on the Kernel module.

Hope you enjoyed this & learned something new!

Please share this post on your favorite social networks now so more people can learn ๐Ÿ™‚

Leave a Comment: