Sexy URLs, or, Default Routes Considered Harmful
Generating anonymous routes is very freaking expensive because Rails has to try generating every possible route you have listed. Use either named routes or helper/model methods if you’ve got lots of links on a page and are worried about performance.
URLs are first sight of your app’s GUI — If every Rails developer said that to themselves, there wouldn’t be a deployed default routes.rb left on the Internet. A URL is not just an arbitrary string of characters which identifies a resource on the Internet. A URL does not merely identify, it also describes what comes behind it, both to end users and, critically, to search engine crawlers. Take a look at these two URL pairs:
* www.smarteguru.com/card/show/116
* www.smarteguru.com/category/show/16
What is behind those URLs? I don’t know — and I wrote the app! Let’s rewrite so that visitors and Google know what they’re getting:
* www.smarteguru.com/article/geography/european-capitals
* www.smarteguru.com/article/geography
Much better! Now anyone knows what those two URLs are about — the first offeres European Capitals bingo cards, the second offers geography bingo cards. What is more, these URLs map to the problem domain. You and Googlebot can both clearly tell by the folder metaphor that European Capitals belongs_to Geography, whereas before that was buried in our user-invisible Ruby code. Additionally, while you have almost certainly chosen your identifiers for convenience for you, your publicly visible URLs should be convenient to your users. You wouldn’t use variable names as UI labels, why would you print them in URLs? I would have gone spare typing BingoCard.find_by_bingo_card_name all the time while writing my controllers, but there is no reason my laziness should result in URLs which don’t tell the user what kind of “card” they are getting. Although, to be fair, with the domain they should have a pretty good idea.
How I Did It: This is simplicity itself. Either use the Permalink-fu plugin and do some mild hackery, or add the following method to your models:
view plaincopy to clipboardprint?
def to_param
url #replace with anything that makes a URL-encoded string
end
def to_param
url #replace with anything that makes a URL-encoded string
end
This will make Rails default to searching for your objects, and writing routes to them, by their slugs (whatever you wrote to_param as) instead of their IDs. Note that if you put an index on whatever column is used to generate the slug, this is no slower than searching on numeric IDs, but it sure looks nicer. If you’re lazy you can prepend the ID to the slug, which will cause Rails to search on the ID.
Note that you will have to write custom routes to go with this, and you will have to be explicit about creating URLs with url_for and the like. I like naming mine so that I can generate them quickly and never get an ugly route by mistake:
view plaincopy to clipboardprint?
#goes in routes.rb
map.showCategory 'article/:category', :controller => 'category', :action => 'show'
map.showCard 'article/:category/:url', :controller => 'card', :action => 'show'
Since I’m lazy I gave Card a helper method which writes a show URL for the appropriate instance, but the canonical way is to pass everything that shows up in the route, like so
view plaincopy to clipboardprint?
link_to "Put My Anchor Text Here", showCard_url(:url => @card.url, :category => @card.category)
Related Posts
Tags: Googlebot, routes, ruby on rails tips, Search Engine Optimization, seo in ror, seo tips in rails
Viewed: 704 views

September 25th, 2008 at 3:18 am
Check out this routing plugin, it lets you do the same thing restfully
http://github.com/caring/default_routing/tree/master
September 25th, 2008 at 8:33 am
This works great of course until you rename your category. Now you got a bunch of broken links. The id should always be in the URL. Even if you have other information for the user. So:
/article/5-geography/13-european-capitals
Is a much better URL because you can do the find with the ID but still have something nice to look at in the URL. Since you are finding by the ID the name can change as it is just for the user. There are a few plugins that will do this automatically for you.
September 25th, 2008 at 1:52 pm
I’d stay in the habit of using _id param names in routes: article/:category_id. The category update action would use params[:category] for the category options. The param is still an identifier, even if isn’t numerical.
Eric: permalink_fu won’t reset your permalink (by default) for that very reason. However, there’s another couple reasons why your idea is preferred (IMO). 1) You can fetch by ID now by calling #to_i on the parameter. Take advantage of the default pkey index. 2) Since the permalink doesn’t matter, we don’t care about unique permalinks anymore. use `has_permalink :foo, :unique => false`. It won’t create unique permalinks like ‘foo’, ‘foo-1′, ‘foo-2′ anymore.
September 26th, 2008 at 3:11 pm
I just don’t get it. To me, a URL is something I click on or, at worst, copy and paste. I don’t expect meaning, so I don’t look for it.
Do users really care about this?
September 29th, 2008 at 2:18 am
If you consider search engines to be users, sure. Obviously if your pages are private, this doesn’t mean a whole lot. Saying ‘Default Routes Considered Harmful’ is definitely a bit extreme. This is just one more tool on the toolbelt.
One thing to keep in mind is you may not want users to be able to extrapolate the number of users or widgets in your system by looking at the sequential IDs. Depending on your business, that may be potentially protected information.
September 29th, 2008 at 12:13 pm
Yeah, we use GUIDs for ids in publically visible URLs.