Error Checking and Debugging with Ruby on Rails - 15.23 Using breakpoint in Your Web Application
(Page 4 of 4 )
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 application--within 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
* Python, for instance, has several excellent web application frameworks, but that’s just the problem. It has several, and a powerful community is fractured on the issue of which to use. Ruby has no major web application frameworks apart from Rails. In a sense, Ruby’s former obscurity is what made the dominance of Rails possible.
* You could throw an exception, but then your redirect wouldn’t happen: the user would see an exception screen instead.
* More precisely, our models have been embedded in our controllers, as ad hoc data structures like hardcoded shopping lists.
* The helper function time_ago_in_words() calculates how long it’s been since a certain time and returns English text such as “about a minute” or “5 hours” or “2 days”. This is a nice, easy way to give the user a perspective on what a date means.
* Rails extends Ruby’s numeric classes to include some very helpful methods (like the hour method shown here). These methods convert the given unit to seconds. For example, Time.now + 1.hour is the same as Time. now + 3600, since 1.hour returns the number of seconds in an hour. Other helpful methods include minutes, hours, days, months, weeks, and years. Since they all convert to numbers of seconds, you can even add them together like 1.week + 3.days.
* This doesn’t quite stand for Asynchronous JavaScript and XML. The origins of the term Ajax are now a part of computing mythology, but it is not an acronym.
* This will happen if someone’s using your application with JavaScript turned off.
* You can even add your web interface actions to the ItemController class. Then a single controller will implement both the traditional web interface and the web service interface. But you can’t define a web application action with the same name as a web service action, because a controller class can contain only one method with a given name.
| DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware. |
|
This article is excerpted from chapter 15 of the Ruby Cookbook, written by Lucas Carlson and Leonard Richardson (O'Reilly, 2006; ISBN: 0596523696). Check it out today at your favorite bookstore. Buy this book now.
|
|