Skip to content

MR06 Inspecting Our Application

Dave Strus edited this page Feb 5, 2016 · 3 revisions

Inspecting our Application

In this unit, we'll do the following:

  • Use Pry to inspect our script at runtime

We'll learn about the following concepts or tools:

  • Pry
  • defined?
  • main
  • Local variables

Assessing the current situation

When Matz created Ruby, he did so in part because he wanted a truly object-oriented language that retained the practical utility of Perl.

Even though we haven't done much to make our code object-oriented, everything is still running in the context of an object. We can prove this by taking a closer look at our script at runtime.

Installing Pry

Pry is a popular alternative to IRB for running Ruby in an interactive shell. Among many other enhancements, it can be invoked at runtime as a console or debugger. We're going to take advantage of that to get a closer look at what's happening in roster.rb.

Install the pry gem if you don't already have it.

$ gem install pry

Using binding.pry

Now we need to require it in our script...

#!/usr/bin/env ruby
require 'pry'

...and invoke it with binding.pry. That effectively creates a breakpoint. At runtime, execution will stop at that point, and we'll be placed into an interactive console.

#!/usr/bin/env ruby
require 'pry'
puts 'Hello, mutant collector!'

print 'Real name: '
real_name = gets.chomp

binding.pry

print 'Mutant name: '
mutant_name = gets.chomp
print 'Power: '
power = gets.chomp
puts "#{mutant_name} (also known as #{real_name}) has an incredible power: #{power}."

Notice I added binding.pry after assigning real_name. Let's run it!

binding.whaaa? At any given point, Ruby will have a binding object that encapsulates the current execution context—variables, methods, all that good stuff. It's an instance of the class Binding, and it can be passed around like any other object if the need arises. Pry adds a pry method to every object to give the interactive console a context in which to run. Most of the time, we want the context in which our code in running at any given point, so it makes sense to start it with the current binding.

$ ./roster.rb
Hello, mutant collector!
Real name: Claudette
From: /Users/dstrus/code/salesforce/later/roster/roster.rb @ line 8 :

     3: puts 'Hello, mutant collector!'
     4:
     5: print 'Real name: '
     6: real_name = gets.chomp
     7:
 =>  8: binding.pry
     9:
    10: print 'Mutant name: '
    11: mutant_name = gets.chomp
    12: print 'Power: '
    13: power = gets.chomp

[1] pry(main)>

The script executes until just after I enter a real name. Then it stops execution and gives me an interactive shell at line 8. Everything prior to that point has already run, so real_name should be set. Let's confirm.

[1] pry(main)> real_name
=> "Claudette"

Sure enough! Let's also confirm that mutant_name is not set.

[2] pry(main)> mutant_name
=> nil

It's nil, but that could mean that it's defined, and its value happens to be nil. Let's use defined? to get a more precise answer.

[3] pry(main)> defined? mutant_name
=> "local-variable"

Well, how about that?

Ruby effectively moves all local variable definitions to the top of the current scope, while leaving the assignments where they are. So if at any time in your current scope, a variable name appears on the left side of an assignment, Ruby will consider that variable defined, with a value of nil, even if that line of code hasn't executed yet.

Knowing that, we can expect power to be defined and set to nil as well.

[4] pry(main)> defined? power
=> "local-variable"
[5] pry(main)> power
=> nil

If we check a variable name that never appears in our scope, we should see that it's not defined.

[6] pry(main)> defined? age
=> nil

Notice that defined? returns nil if something isn't defined. Now what happens if we try to use this non-existent variable?

[7] pry(main)> age
NameError: undefined local variable or method `age' for main:Object
from (pry):7:in `<main>'

I've made a couple of vague references to the "current scope", but what is the current scope? The output of this last experiment gives us a clue. Ruby was looking for a local variable or method called age on main:Object.

We've seen that the variables we've created thus far are not global, but are local. Local to what? Let's check the value of self to get our bearings.

[8] pry(main)> self
=> main

Sure enough, we're in main. Our error message suggested that main is an instance of Object. Let's confirm.

[9] pry(main)> self.class
=> Object

Sure enough. So any methods or variables we define are in this object. We're doing everything within an object whether we know it or not.

It seems like a good idea to to be more deliberate about this. Let's make our own class and extract our script's behavior into that new class.

Before moving on, remove binding.pry from your script so it will execute from start to finish.