I was working on a large community-driven website recently, which had always had the requirement to synchronize its user database with another (3rd party) site that we didn't control. The most we could get out of the other site was for them to send us regular XML dumps of the changes (additions, removals, deletions). Predictably, we wrote that as a rake task and added it to cron.
@hourly cd /apps/product/current && export RAILS_ENV=production && rake product:synchronize_database
It worked great, until the client threw in ONE additional little snag; not only should it check for updates every hour, but an admin should be able to log in to the site and request an immediate synchronization. Unfortunately, this task can take anywhere from 10 minutes to 10 hours, depending on the size of the XML file and the number of employees involved. Clearly not a job for a simple backtick or %x{}.
There are a number of different options for running background processes in Rails, but since I had pretty simple requirements (and needed quick results) the best choice for me was clearly Spawn.
You can install the plugin from rubyforge:
script/plugin install http://spawn.rubyforge.org/svn/spawn/
Then implementing the client's request was as simple as adding a button with a remote_function and a controller action with:
spawn(:nice => 7) do exec("cd #{RAILS_ROOT} && export RAILS_ENV=#{RAILS_ENV} && rake product:synchronize_database") end
The :nice option (vital, in my case!) will make sure that your process doesn't monopolize the CPU (just like its shell counterpart). There are only a couple of other options; you can choose to fork or thread your process (fork is the default), and you can wait for it to finish. Neither was necessary in my case. Problem solved and, in typical Rails fashion, it only took a few lines of code!
Even the most experienced developer will sometimes encounter abnormal behavior from what they believe to be perfectly valid code. script/console is fantastic and extremely helpful for some of those situations, but there are times when it's not enough. You may need more context in order to discover the source of your problem, and that's where ruby-debug comes in.
Getting Ready to Debug
In order to debug your Rails application the first thing you need to do is to install the ruby-debug gem.
sudo gem install ruby-debug -y
Next, you need to set a breakpoint in your application by dropping this cleverly named nugget debugger into your code where you would like to begin the analysis of your app. For simplicity's sake I will just put the debugger in the show action of my PropertiesController.
# GET /properties/1
# GET /properties/1.xml
def show
debugger
@property = @manager.properties.find(params[:id], :include => [:municipality, {:assemblies => :assembly_checks}])
...
end
And now the only thing left is to just start the server with debugging enabled.
script/server --debugger
Easy Peazy.
In order for that breakpoint to actually break though, the debugger statement needs to be evaluated. We can accomplish this by navigating our browser to the show action where the debugger is set. You will notice that once we do this our browser becomes unresponsive. This is because the debugger halts the execution of our application at the breakpoint until we tell it to proceed. So while we are waiting on our browser, why don't we take a look at the terminal.
/Users/joshua/Sites/backflow_testing_app/app/controllers/properties_controller.rb:19
@property = @manager.properties.find(params[:id], :include => [:municipality, {:assemblies => :assembly_checks}])
(rdb:2)
That's kind of cool, we have a nice prompt there just waiting for us to use.
Getting a Lay of the Land
By typing l (or list) at the prompt we can take a look at the first ten lines of code surrounding our debugger statement. Each successive l command will display the next ten lines of code in the current file.
(rdb:2) l
[14, 23] in /Users/joshua/Sites/backflow_testing_app/app/controllers/properties_controller.rb
14
15 # GET /properties/1
16 # GET /properties/1.xml
17 def show
18 debugger
=> 19 @property = @manager.properties.find(params[:id], :include => [:municipality, {:assemblies => :assembly_checks}])
20 @testers = User.find(:all, :conditions => ["is_tester = ?", true])
21 @next_test_date = @property.assemblies.first.next_test_date if @property.assemblies.first
22
23 @next_test_date =
(rdb:2) l
[24, 33] in /Users/joshua/Sites/backflow_testing_app/app/controllers/properties_controller.rb
24 respond_to do |format|
25 format.html # show.html.erb
26 format.xml { render :xml => @property }
27 end
28 end
29
30 # GET /properties/new
31 # GET /properties/new.xml
32 def new
33 @property = Property.new
(rdb:2)
l - will move us back up 10 lines in the file and l = will always return us to the current line or the line to be evaluated next. The current line is designated by the =>. If we want to have a larger context to look at we can specify a range of line numbers and pass that to l, like so l 1-40. If you happen to be using Textmate we can even type tmate and view the entire file in all its colorful glory.
So now that we know where we are in terms of where we are in the file, let's get a sense of where we are from a different perspective. Let's see where we are in terms of the stack trace. We can view the stack trace by typing either backtrace or where. This prints the entire stack trace to the screen with each stack frame numbered from the current frame (0) upwards. We can navigate through the stack by either explicitly selecting the frame to jump to (e.g. frame 20) or by moving a relative number of frames up or down from our current frame. For example, if we are inspecting frame 5, up 5 or down 2 will move us to frames 10 and 3 respectively. The =>, as before, will show us our current location.
Step Into, Step Over and Continue
Let's make some progress and move forward through some of our code. Let's first make certain we are on the current frame by typing frame 0. We can step through our code by using one of two methods - either n (next) or s (step). n is the traditional step over and s is the traditional step into - if we want to dive into the guts of our methods. If we want to move more quickly through our code, perhaps to the next breakpoint, we can use the c (continue) command.
So let's give it a try. Let's just move to the next line of code using n and inspect the @propertyobject using the p command to print to screen.
[14, 23] in /Users/joshua/Sites/backflow_testing_app/app/controllers/properties_controller.rb
14
15 # GET /properties/1
16 # GET /properties/1.xml
17 def show
18 debugger
=> 19 @property = @manager.properties.find(params[:id], :include => [:municipality, {:assemblies => :assembly_checks}])
20 @testers = User.find(:all, :conditions => ["is_tester = ?", true])
21 @next_test_date = @property.assemblies.first.next_test_date if @property.assemblies.first
22
23 @next_test_date =
(rdb:2) n
/Users/joshua/Sites/backflow_testing_app/app/controllers/properties_controller.rb:20
@testers = User.find(:all, :conditions => ["is_tester = ?", true])
(rdb:2) p @property
#<Property id: 690789713, manager_id: 476927875, integer: nil, name: "Borders Books", string: nil, store: "0020", contact: "jon", street_address: "1500 West 16thStreet", city: "Oak Brook", state: "IL", zip_code: "60523", phone: "(630)574-0800", fax: "", email: "someone@borders.com", municipality_id: 1847418, previous_manager: nil, notes: "", text: nil, property_deleted: false, boolean: nil, lat: #<BigDecimal:189a430,'0.41853303E2',12(16)>, decimal: nil, lng: #<BigDecimal:189a0ac,'-0.87997248E2',12(16)>, created_at: "2008-06-14 17:50:39", updated_at: "2008-06-14 18:25:36">
One interesting and occasionally helpful thing to note is that we can make assignments while debugging.
(rdb:2) @property.name = "Josh\'s Books" "Josh's Books" (rdb:2)
Setting Breaks while Debugging
We are not limited to the breakpoints we put in our code (i.e. debugger). We can also set conditional or unconditional breakpoints from within the debugger. The b (break) command takes either a file and a line number or a class and a method as arguments. So if we wanted to add a breakpoint on the edit and delete actions of the PropertiesController for instance we could simply enter this.
(rdb:716) break PropertiesController.edit Breakpoint 1 at PropertiesController::edit (rdb:716) break PropertiesController.delete Breakpoint 2 at PropertiesController::delete
And to delete the edit breakpoint simply specify the number and delete it like so.
(rdb:1132) delete 1
Now let's list our single remaining breakpoint with info breakpoints.
(rdb:1132) info breakpoints Num Enb What 2 y at PropertiesController:delete
irb
The debugger offers us a lot but lacks some of the functionality of script/console or irb. It turns out, however, that that's not really a problem, we can get the functionality of irbeasily enough. By simply typing irb at the debugger prompt we can open an irb session. In this session we have the bindings environment set to the current debugging state of your app and are able to alter the behavior of the application. Once we have done whatever we need in irb we can simply type quit and we will be returned to the same line in the debugger as when we entered irb.
Don't Forget the Help
This is by no means an exhaustive examination of the debugger but rather is a brief introduction to the uninitiated. Don't forget to use h (help) and h command for more information. I also would recommend trying out the cheat gem for a quick reference of this and other helpful cheat sheets.
sudo gem install cheat cheat rdebug

