Wednesday, January 16, 2008

Hyphenated URLs in Rails

The Problem

Rails, by default, produces underscored URLs for multi-word methods and controllers. For example, a controller by the name of ContactUs with the method about_advertising will respond, with the default routing, to the path /contact_us/about_advertising. This is all well and good except when it comes to search engines. Underscores are seen, at least by Google at the moment, as part of a word, whereas hyphens (dashes) are seen as word separators. This means that our above path will hit for searches on "contact_us" and "about_advertising" but not for "contact" or "advertising". That's no good! How can we get Rails to use /contact-us/about-advertising so that our site might get indexed for the keyword "advertising"?

Action Names

Fixing the action name is easy enough as hyphens are valid characters in method names. Well, almost-- hyphens may be valid in method names, but they are not valid in symbols, which means that we can't use the standard "def" syntax to declare methods with hyphens in their names. To get around that, we turn to define_method. To change /contact_us/about_advertising to /contact_us/about-advertising, we can change the method declaration from:
def about_advertising
...
end
to:
define_method('about-advertising') do
...
end

Controller Paths

It looks like we are halfway to where we want to go, but the controller part of the path turns out to be a more difficult part of the problem. There is no way, at least that I know of, to use an underscore in a class name. Therefore, we turn to Rails routing for some help. The easiest solution is to create a special route for the ContactUs controller in config/routes.rb as follows:


ActionController::Routing::Routes.draw do |map|
map.connect '/contact-us/:action/:id', :controller => 'contact_us'
end

and we get /contact-us/about-advertising. And we're done! Well, not quite. Having to add route entries for every multi-word controller is no fun, certainly not very DRY, and doesn't handle the RESTful map.resources. Enter hyphenated_controller_routes, the plugin I wrote to address those problems, and the reason for this blog post.

Hyphenated Controller Routes

The plugin, located at http://svn.vickeryj.com/public/hyphenated_controller_routes/trunk can automatically create hyphenated routes for all multi-word controllers, and can update map.resources to generate hyphenated URLs for multi-word resources. After installing the plugin, update config/routes.rb to make use of map.add_hyphenated_routes() and map.use_hyphenated_resources() as desired.

An example config/routes.rb:

ActionController::Routing::Routes.draw do |map|

#a resource that will be mapped with an underscore
map.resources :non_hyphenated_resources

#turn on hyphenated resources
map.use_hyphenated_resources()

#all following resources will use hyphens (contact-us)
map.resource :contact_us

# add hyphenated routes with higher priority than the default routes
map.add_hyphenated_routes

# Install the default routes
map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'

end