Monday, December 21, 2009

Recipe 15.23. Using breakpoint in Your Web Application










Recipe 15.23. Using breakpoint in Your Web Application







Problem


Your Rails application has a bug that you can't find using log messages. You need a heavy-duty debugging tool that lets you inspect the full state of your application at any given point.




Solution


The
breakpoint
library lets you stop the flow of code and drop into irb, an interactive Ruby session. Within irb you can inspect the variables local to the current scope, modify those variables, and resume execution of the normal flow of code. If you have ever spent hours trying to track down a bug by placing logging messages everywhere, you'll find that breakpoint gives you a much easier and more straightforward way to debug.


But how can you run an interactive console program from a web application? The answer is to have a console program running beforehand, listening for calls from the Rails server.


The first step is to run ./script/breakpointer on the command line. This command starts a server that listens over the network for breakpoint calls from the Rails server. Keep this program running in a terminal window: this is where the irb session will start up:



$ ./script/breakpointer
No connection to breakpoint service at druby://localhost:42531
Tries to connect will be made every 2 seconds…



To trigger an irb session, you can call the breakpoint method anywhere you like from your Rails applicationwithin a model, controller, or helper method. When execution reaches that point, processing of the incoming client request will stop, and an irb session will start in your terminal. When you quit the session, processing of the request will resume.




Discussion


Here's an example. Let's say you've written the following controller, and you're having trouble modifying the name attribute of an Item object.



class ItemsController < ApplicationController
def update
@item = Item.find(params[:id])
@item.value = '[default]'
@item.name = params[:name]
@item.save
render :text => 'Saved'
end
end



You can put a breakpoint call in the Item class, like this:



class Item < ActiveRecord::Base
attr_accessor :name, :value

def name=(name)
super
breakpoint
end
end



Accessing the URL http://localhost:3000/items/update/123?name=Foo calls Item-Controller#update, which finds Item number 123 and then calls its name= method. The call to name= triggers the breakpoint. Instead of rendering the text "Saved", the site seems to hang and become unresponsive to requests.


But if you return to the terminal running the breakpointer server, you'll see that an interactive Ruby session has started. This session allows you to play with all the local variables and methods at the point where the breakpoint was called:



Executing break point "Item#name=" at item.rb:4 in 'name='
irb:001:0> local_variables
=> ["name", "value", "_", "__"]
irb:002:0> [name, value]
=> ["Foo", "[default]"]
irb:003:0> [@name, @value]
=> ["Foo", "[default]"]
irb:004:0> self
=> #<Item:0x292fbe8 @name="Foo", @value="[default]">
irb:005:0> self.value = "Bar"
=> "Bar"
irb:006:0> save
=> true
irb:006:0> exit
Server exited. Closing connection…



Once you finish, type exit to terminate the interactive Ruby session. The Rails application continues running at the place it left off, rendering "Saved" as expected.


By default, breakpoints are named for the method in which they appear. You can pass a string into breakpoint to get a more descriptive name. This is especially helpful if one method contains several breakpoints:



breakpoint "Trying to set Item#name, just called super"



Instead of calling breakpoint directly, you can also call assert, a method which takes a code block. If the block evaluates to false, Ruby calls breakpoint; otherwise, things continue as normal. Using assert lets you set breakpoints that are only called when something goes wrong (called "conditional breakpoints" in traditional debuggers):



1.upto 10 do |i|
assert { Person.find(i) }
p = Person.find(i)
p.update_attribute(:name, 'Lucas')
end



If all of the required Person objects are found, the breakpoint is never called, because Person.find always returns true. If one of the Person objects is missing, Ruby calls the breakpoint method and you get an irb session to investigate.


Breakpoint is a powerful tool that can vastly simplify your debugging process. It can be hard to understand the true power of it until you try it yourself, so go through the solution with your own code to toy around with it.




See Also


  • Recipe 17.10, "Using breakpoint to Inspect and Change the State of Your Application," covers breakpoint in more detail.

  • http://wiki.rubyonrails.com/rails/show/HowtoDebugWithBreakpoint













No comments: