Lesson Weekend

As you write more complex code, you'll run into increasingly difficult bugs and problems. So, in this lesson, we'll cover two helpful debugging tools:

  • Using error messages to solve bugs
  • Using Pry, a Ruby gem, to inspect variables during code execution.

To show how to use these tools, we'll use our code from the title_case project.

Reading Error Messages


We'll start with debugging our methods and specs. Let's look at our title case method again. This time, we've added some errors to debug. Go ahead and copy and paste the following code into your title_case project.

lib/title_case.rb
def title_case(sentence)
  split_sentence = sentence.splt()
  split_sentence.each() do |word|
    word.capitalize()
  end
  split_sentence.join(" ")
end
spec/title_case_spec.rb
require('rspec')
require('title_case')

describe('title_case') do
  it("capitalizes the first letter of a word") do
    expect(title_case("beowulf")).to(eq(Beowulf))
  end

  it("capitalizes the first letter of all words in a multiple word title") do
    expect(title_case("the color purple")).to(eq("The Color Purple"))
  end
end

Fixing the First Error

If we run RSpec, we get the error: NoMethodError: undefined method 'splt' for "beowulf":String. We also get the following message, which provides further clues on the location of the error: # ./lib/title_case.rb:2'.

Failures:

  1) title_case capitalizes the first letter of a word
     Failure/Error: split_sentence = sentence.splt()

     NoMethodError:
       undefined method `splt' for "beowulf":String

         split_sentence = sentence.splt()
                                  ^^^^^
       Did you mean?  split
     # ./lib/title_case.rb:2:in `title_case'
     # ./spec/title_case_spec.rb:6:in `block (2 levels) in <top (required)>'

In Ruby, errors are actually objects, and NoMethodError refers to the error's class. According to the error, splt in undefined or doesn't exist. The message specifies that the error is occurring on line 2 of title_case.rb. Sure enough, it looks like we misspelled the split() method.

Fixing the Second Error

If we fix that and run RSpec again, we now get the error: NameError: uninitialized constant Beowulf # ./spec/title_case_spec.rb:8.

Failures:

  1) title_case capitalizes the first letter of a word
     Failure/Error: expect(title_case("beowulf")).to eq(Beowulf)

     NameError:
       uninitialized constant Beowulf

           expect(title_case("beowulf")).to eq(Beowulf)
                                               ^^^^^^^
     # ./spec/title_case_spec.rb:6:in `block (2 levels) in <top (required)>'

Ruby throws an uninitialized constant because it thinks Beowulf is the name of a class that hasn't been defined yet. The next line tells us to look at our spec file on line 6. We forgot quotation marks around Beowulf. Let's add the quotation marks and run RSpec.

Unfortunately, we still get an error: expected: "Beowulf" got: "beowulf".

Failures:

  1) title_case capitalizes the first letter of a word
     Failure/Error: expect(title_case("beowulf")).to eq("Beowulf")

       expected: "Beowulf"
            got: "beowulf"

       (compared using ==)
     # ./spec/title_case_spec.rb:6:in `block (2 levels) in <top (required)>'

This error message isn't as helpful as the previous two because it doesn't tell us what's wrong or where the error is. Our method just isn't working. It would be really helpful if we could peek inside our code as it was running to see what values each variable held. Well, that's exactly what the next debugging tool will help us do.

Using Pry to Debug Code


Pry is one of the most useful debugging tools at our disposal. We can use it much the way we used debugger; in JavaScript.

In order to use Pry, we need to add gem 'pry' to our Gemfile and then $ bundle.

Gemfile
source 'https://rubygems.org'

gem 'rspec'
gem 'pry'

Then, we'll need to add require('pry') to the top of your title_case.rb file (or any other file where you'd like it to be available).

To experiment with Pry, change your title_case.rb file to the following:

lib/title_case.rb
require('pry')

def title_case(sentence)
  split_sentence = sentence.split()
binding.pry
  split_sentence.each() do |word|
    word.capitalize()
  end
  split_sentence.join(" ")
end

binding.pry adds a breakpoint to our code. Now, when we run rspec, the code will pause execution when it hits binding.pry. One thing to note here: if RSpec doesn't pause, that means RSpec isn't reaching the breakpoint, which in itself is a helpful debugging clue.

We can now run any code we like in the prompt that Pry provides. Let's check the value of split_sentence by typing it into the prompt.

☺☻From:☺☻ C:/Users/Staff/Desktop/title_case/lib/title_case.rb:5 Object#title_case:  

     3: def title_case(sentence)
     4:   split_sentence = sentence.split()
 =>  5: binding.pry
     6:   split_sentence.each() do |word|
     7:     word.capitalize()
     8:   end
     9:   split_sentence.join(" ")
    10: end

[1] pry(#<RSpec::ExampleGroups::TitleCase>)> split_sentence
=> ["beowulf"]

As we can see, split_sentence returns ["beowulf"]. So far, we haven't learned anything new, so let's move the binding.pry into the each() loop.

Before we do that, we need to type exit to leave the Pry prompt.

lib/title_case.rb
require('pry')

def title_case(sentence)
  split_sentence = sentence.split()
  split_sentence.each() do |word|
binding.pry            # we've moved Pry into .each() 
    word.capitalize()
  end
  split_sentence.join(" ")
end

Now run RSpec again and let's check the value of word.capitalize().

☺☻From:☺☻ C:/Users/brook/Desktop/ruby_projects/title_case/lib/title_case.rb:6 Object#title_case:  

     3: def title_case(sentence)
     4:   split_sentence = sentence.split()
     5:   split_sentence.each() do |word|
 =>  6:     binding.pry
     7:     word.capitalize()
     8:   end
     9:   split_sentence.join(" ")
    10: end

[1] pry(#<RSpec::ExampleGroups::TitleCase>)> word.capitalize()
=> "Beowulf"

It returns "Beowulf", which is what we'd expect. If we check the value of word, though, Pry returns "beowulf".

[2] pry(#<RSpec::ExampleGroups::TitleCase>)> word
=> "beowulf"

The String#capitalize method doesn't change the receiver, so word isn't being capitalized. Let's use the bang version String#capitalize! instead.

lib/title_case.rb
def title_case(sentence)
  split_sentence = sentence.split()
  split_sentence.each() do |word|
    word.capitalize!()
  end
  split_sentence.join(" ")
end

Now when we run $ rspec, our tests should be green.

Final Thoughts


Once we've debugged our code, we should remove binding.pry. Otherwise, our application will freeze whenever we hit that breakpoint again.

The Pry gem is very helpful and binding.pry can be used anywhere in your Ruby code. Often you'll use binding.pry in your lib files, but it can also be used in spec files as well.

Because Pry is one of the most important debugging tools you can use in Ruby, you should practice with it often. In fact, you should always try debugging with Pry before reaching out for help from peers or your teacher. If your code doesn't hit the breakpoint, then you know that part of your code isn't being accessed. That can be helpful with debugging conditionals or when your application hits a fatal error before the breakpoint.

When your code does reach the breakpoint, you can check the value of any variables or methods to verify the code is working as expected. You can even run any additional code you like, which means you can use Pry to experiment and test new code in the exact environment where it will be used. If your code is functioning as expected in the breakpoint, your next step is to exit the breakpoint and move binding.pry down a line, testing the code once again. In this way, you can systematically work your code until you find the source of the bug.

You now have some basic debugging tools at your disposal. It's time to start coding. If you run into frustrating errors, there's a silver lining. They provide a great opportunity to practice debugging. Ultimately, learning to debug is just as important as learning to write code!

Using Pry


  • Add to Gemfile (gem 'pry') and bundle.
  • Add require('pry') to top of spec file.
  • Add binding.pry to put a breakpoint in the spec file or the corresponding lib file of the same name.

Lesson 4 of 4
Last updated August 7, 2022