Debugging Support in Edge Rails

Posted by Mike
Liquid error: wrong number of arguments (5 for 2)

One of the things that DHH announced as a feature for Rails 2.0 in yesterday’s keynote was “breakpoints are back.” Given that I’m still at the stage in my Rails journey where my applications tend to spit out large stack traces at a moment’s notice, this was definitely of interest to me. So I took a few minutes today to find out how easy it was to do this with the current cutting-edge bits. The answer turns out to be, pretty easy. If you want to poke at this stuff today, here’s what you need to do.

1. Get on to Edge Rails (anything more recent than Changeset 6611 will do).

2. Install the ruby-debug gem:


sudo gem install ruby-debug

3. Build your Rails application (this step left as an exercise for the reader).

4. Start up your development server with


ruby script/server --debugger

If you haven’t looked at Edge lately, this now starts Mongrel by default instead of WEBrick.

5. Anywhere in your source where you want to fall out into irb, insert the single line of code


debugger

6. Browse to a page that will hit the debugger statement and then flip over to the terminal window where you started the server. You’ll be sitting at an irb prompt. You can evaluate expressions, inspect and change objects, and so on. Type cont to continue on with execution.

7. ruby-debug supports a bunch of other commands. To drop from irb into the debugger proper, type Ctrl-d. Type help to see the command list. Type irb to return to irb (and then cont to go on with your application’s business).

Basic Routing in Rails

Posted by Mike
Liquid error: wrong number of arguments (5 for 2)

Coming (as I did) from an ASP.NET background, the support for routing in Rails was one of many aspects of this new world that impressed me considerably. It’s easy to see why that should be so: the support for a similar facility in ASP.NET (mapping of friendly URLs to internal URLs) is rudimentary at best. The reasons why point up the difference in approach between the two development teams: Microsoft has apparently chosen to avoid shipping any usable URL rewriting code until they can bombproof it for every conceivable use and size of enterprise, while the Rails team has chosen to give us something that’s good enough for most applications right now.

The point of routing is quite simple: it decouples the internal design of your Rails application from its external interface. For example, it may make perfect sense to you to have all of the methods that deal with handling image management, including the administrative tools for uploading images and a method for showing images, within a controller named upload. But that doesn’t mean that referring to an image within your application must be done with a URL such as /upload/show/4. With routing, you are perfectly free to use a URL such as /images/4 instead.

To do this, you define a route within the file config/routes.rb in your application. In this particular case, the route would look like this:


# show a single image
 map.connect '/images/:id', :controller => 'upload', :action => 'show'
 

You can think of the routes.rb file as a set of rules that is followed by Rails in dispatching incoming HTTP requests to the controllers that will handle them. Rails scans down these rules in order and sends each request to the first matching rule. In this particular case, the rule matches any incoming request for the exact text /images/ followed by precisely one word (in the regular expression sense of a contiguous set of letters and numbers). The word becomes the :id parameter which is passed to the show action within the upload controller. So this particular route would be invoked for /images/16 but not for /images/16/172.

As it stands, this route would also be invoked for /images/wintergreen.jpg, which is a problem if your show action is expecting to be passed an :id parameter. Generally it’s considered poor form for your application to blow up in the user’s face. To avoid having routes try to process requests that they can’t handle, you can qualify them further with requirements expressed as regular expressions. In this particular case, we can add the requirement that the :id parameter be numeric:


map.connect ’/images/:id’, :controller => ‘upload’, :action => ‘show’, 
                           :requirements =>{ :id => /\d+/ }

This can be expressed more concisely as:


# show a single image
 map.connect ’/images/:id’, :controller => ‘upload’, :action => ‘show’, :id => /\d+/

But hold on a minute here: surely in a well-designed application, you’d like to allow people to retrieve images by name as well as by id. That’s certainly possible; all that you need to do is define another method (let’s call it find_by_name) in the controller and add another route. In this case, though, you must include requirements in the route. Why? Because by default, the dot character, like the forward slash, is treated as a separator between elements in a route. So you need to explicitly tell Rails that you want the dot treated as part of a parameter:


# show a single image by name
map.connect ’/images/:imagename’, :controller => ‘upload’, :action => ‘find_by_name’, 
                                              :imagename => /.*/   

Another potential use of routes lies in protecting some URLs from being inadvertently called in a way that can lead to unwanted side-effects. You can use conditions to route requests differently depending on which HTTP verb is used to invoke them. For example, consider this pair of routes:


map.connect 'admin/update/:id', :conditions => { :method => :get }, 
                                :controller => "admin", :action => "list" 
map.connect 'admin/update/:id', :conditions => { :method => :post }, 
                                :controller => "admin", :action => "update" 

If a URL such as /admin/update/7 is called via an HTTP GET (perhaps as a result of an overenthusiastic search-engine listing), the first route will redirect this to the list action. Only requests that come in via POST (presumably from an editing form) will be processed by the second route and sent to the update action.

Routes can be made even more flexible with the addition of defaults. Supplying defaults allows you to create routes where trailing portions of the URL can be omitted. Going back to the image handling example, suppose you wanted to show an image catalog when the plain /images URL is accessed? Here’s a route to handle that:


map.connect 'images/:action/:id', :defaults => { :action => "catalog"}, 
                                  :controller => "upload" 

If you don’t supply an :action parameter, the value catalog will be used. There are two special defaults – “default defaults,” if you will. By convention, :action defaults to index and :id defaults to nil. This explains how a single default route can handle the dispatching chores in a simple Rails application:


map.connect ':controller/:action/:id'

Finally (for now), the last element of a route path can be a catch-all element, using the syntax *name. Here’s a route that catches anything, no matter how strangely-composed:


map.connect '*incoming' :controller => "admin", :action => "sitemap" 

The incoming parameter in this case will point to an array containing all of the components of the incoming URL. Because this route will match anything, it must be the last one in your routes.rb file (well, there can be other routes after it, but they will never match anything because Rails processes routes from the top of the file down).


That’s enough to get started with Rails routing for the purpose of URL rewriting, but there’s a lot more to learn. In particular, I haven’t covered the creation and use of named routes, or the use of the new resource-based routing. I may get to them in future installments.