Recipe 10.3. Debugging Your Application in Real Time with the breakpointer


Recipe 10.3. Debugging Your Application in Real Time with the breakpointer

Problem

You have noticed that one of your views is not displaying the data you expected. Specifically, your application should display a list of book chapters, with each list item containing the chapter title and recipe count. However, the recipe count is off, and you want to find out why.

You remember that for this particular view, you're building a data structure in the corresponding controller action, and making it available to the view in an instance variable. You need to inspect this data structure. More generally, you want to find out the state of the variables or data structures that are being sent to your views.

Solution

Use the built-in breakpointer client to connect to the breakpoint service that your application starts when it encounters breakpoints in your code. You set breakpoints by calling breakpoint at locations you would like to inspect in real time.

Let's demonstrate using breakpointer to find out why your chapter listings aren't displaying recipe counts correctly. The Chapter model defines a class method, recipe_count, which returns the total number of recipes in each chapter. Here's the model, complete with a bug:

app/models/chapter.rb:

class Chapter < ActiveRecord::Base   has_many :recipes      def recipe_count     Recipe.find(:all, :conditions => ['chapter_id = ?', 1]).length   end end

The list method of your Chapters controller builds a data structure, an array of Arrays, by calling title and recipe_count on every chapter object. This structure is stored in the @chapters instance variable.

app/controllers/chapters_controller.rb:

class ChaptersController < ApplicationController   def list     @chapters = Chapter.find(:all).map {|c| [c.title,c.recipe_count]}   end end

In the list view, you iterate over the arrays in @chapters and display the title, followed by the number of recipes in that chapter. However, in some cases, your view displays the wrong recipe counts.

views/chapters/list.rhtml:

<h1>Chapters</h1> <ol>   <% for name, recipe_count in @chapters %>     <li><%= name %> (<%= recipe_count %>)</li>   <% end %> </ol>

Let's debug the problem using breakpointer. Call breakpoint at the points where you would like execution to stop, while you have a look around. You don't see anything wrong with the code in your view, so the next step up the chain of execution is the Chapters controller. Put the following breakpoint in the list method of the Chapters controller:

def list   @chapters = Chapter.find(:all).map {|c| [c.title,c.recipe_count]}   breakpoint "ChaptersController#list" end

Next, invoke the breakpointer script from the root of your application. (Debugging with breakpointer requires a few setup steps, so just starting the breakpointer client won't do much at first.) Start the script, and you should see the following:

$ ruby script/breakpointer No connection to breakpoint service at druby://localhost:42531 (DRb::DRbConnError) Tries to connect will be made every 2 seconds...

You've started a network client that is attempting to connect to a breakpoint service every two seconds. This is all you will see for now because the breakpoint service is not yet running. Leave this window open; you'll return to it in a moment.

Now, use a browser to visit a page that will trigger the action in which you inserted the breakpoint. To render the list view, navigate to http://localhost:3000/chapters/list. Your browser will appear to hang, as if loading a page that takes forever. Don't worry, this is normal. Just leave the browser alone for now.

Next, look back at the terminal window running the breakpointer client. You should see something like this:

$ ruby script/breakpointer No connection to breakpoint service at druby://localhost:42531 (DRb::DRbConnError) Tries to connect will be made every 2 seconds... Executing break point "ChaptersController#list" at /Users/roborsini/rails-cookbook/    recipes/Debugging/Debugging_Your_Application_in_Real_Time_with_the_Breakpointer/    config/../app/controllers/chapters_controller.rb:5 in 'list' irb(#<ChaptersController:0x224cc0c>):001:0> 

The client has successfully connected to the breakpoint service. You are now dropped into a special irb session that has access to the local variable scope at the point where you put the breakpoint call. From here, you can proceed to inspect the contents of the @chapters variable and try to narrow down why your recipe counts are off.

irb(#<ChaptersController:0x227fb84>):001:0> @chapters         => [["Modeling Data with Active Record", 2], ["Debugging your Rails Apps", 2]]

This output confirms that the recipe count is off in the data structure you're passing to your view. This confirms that the problem really isn't with the code in your view. It must be a problem with the setup of the datastructure or perhaps something further upstream, like the Chapter model. You guess that the problem with the recipe_count method. To verify this, you can examine just how many recipes are in your database.

irb(#<ChaptersController:0x2562dec>):002:0> Recipe.find(:all).length => 5 irb(#<ChaptersController:0x2562dec>):003:0> Recipe.find(:all).map do |r| \                                               [r.title,r.chapter_id] \                                             end => [["Setting up a one-to-many Relationship", 1], ["Validation", 1], ["Using the Breakpointer", 2], ["Inpection with ./script/console", 2], ["Log debugging output with Logger", 2]]

By inspecting all recipe objects in your database, you've found there are actually three recipes in the "Debugging" chapter, not two. You're done with the breakpointer for now. To end the session, press Ctrl-D (Unix) or Ctrl-Z (Windows), which moves you to the next breakpoint in your code. If there are no more breakpoints, the script waits patiently for you to trigger another breakpoint with your browser. But you're done for now, so type Ctrl-C to exit the script. Stopping the breakpoint client lets your browser complete the request cycle.

Armed with a pretty good idea that there's a bug in your model, take a closer look at the recipe_count method in your Chapter model definition:

def recipe_count   Recipe.find(:all, :conditions => ['chapter_id = ?', 1]).length                               # opps, "1" is a hard coded value! end

Sure enough, the hardcoded integer, "1", in recipe_count should have been the id of the receiver of this method, or self.id. Change the method definition accordingly, and test to make sure the bug is resolved.

def recipe_count   Recipe.find(:all, :conditions => ['chapter_id = ?', self.id]).length end

Discussion

Breakpoints in Rails behave just as they do in other debuggers, such as GDB or the Perl debugger. They function as intentional stopping points that temporarily interrupt your application and allow you to inspect the environment local to each one, trying to find out whether your application is functioning as expected.

If you're setting more than one breakpoint in a debugging session, it's helpful to name each breakpoint. Do this by passing a descriptive string with each call:

def list   breakpoint "pre @chapters"    @chapters = Chapter.find(:all).map {|c| [c.title,c.recipe_count]}   breakpoint "post @chapters" end

irb displays the names of the breakpoints as you cycle through them with Ctrl-D (Unix) or Ctrl-Z (Windows):

Executing break point "pre @chapters" at /Users/roborsini/rails-cookbook/recipes/    Debugging/Debugging_Your_Application_in_Real_Time_with_the_Breakpointer/config/..    /app/controllers/chapters_controller.rb:4 in 'list' irb(#<ChaptersController:0x24f1174>):001:0> CTL-D Executing break point "post @chapters" at /Users/roborsini/rails-cookbook/recipes/    Debugging/Debugging_Your_Application_in_Real_Time_with_the_Breakpointer/config/    ../app/controllers/chapters_controller.rb:6 in 'list' irb(#<ChaptersController:0x24f1174>):001:0> 

Notice how the solution starts from the point where the bug or error condition was reported (in the view) and works backward up the Rails stack, toward the model, attempting to isolate the cause of the bug. This kind of methodical approach to debugging works well, and using breakpointer saves a lot of time that might otherwise be spent writing print statements and making educated guesses about there these print statements are best placed. The real power of breakpointer is that you are interacting with your application in real time and can inspect (and even modify) the environment in which the code runs. For example, you could do something like:

Chapter.delete(1)

which deletes the record in your chapters table with an id of 1. As you can see, the environment is "live," so proceed with caution when calling methods that make permanent changes to your model.

See Also

  • For more on use of the Rails breakpointer, see

    http://wiki.rubyonrails.org/rails/pages/HowtoDebugWithBreakpoint



Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net