-
Notifications
You must be signed in to change notification settings - Fork 0
MR06 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
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.
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
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 abinding
object that encapsulates the current execution context—variables, methods, all that good stuff. It's an instance of the classBinding
, and it can be passed around like any other object if the need arises. Pry adds apry
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 currentbinding
.
$ ./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.