Ruby On Rails: Meaningful (and beautiful) URLs
This tutorial is going to take you through what it takes to make your URLs easy to remember and meaningful. Great examples of sites that implement this well are Flickr and Delicious. We are going to first take a look at the Routes framework in the Action Controller and see what options it gives us. I am going to assume basic knowledge of Ruby on Rails and am going to assume that your are starting with a newly generated application. (NOTE: I am using Rails 2.1.1)
Routes in Ruby on Rails allow you to map incoming URLs to specific controllers, actions, and parameters in your application. Routes are written in pure ruby and can be found in the file config/routes.rb in your application’s root directory. Upon generating a new application using rails appname, some default routes are created for you as shown below (comments have been removed):
1 2 3 4 ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end
Routes are created using the connect() method. For more information on the arguments connect() can take, see here. Now that you have a handle on what routes are and what they do, let’s make one of our own.
Now for lack of an example, let’s imagine that we are creating a site that let’s users post and share songs they have made. We have a controller called SongController that contains most of the logic of the application. Our controller, SongController, has an action, display(), that can search for and display songs based on assorted criteria, like the song’s author.
First, I am going to show you how to create a route that will let users display all songs created by a specific author. Let’s assume our site is called SongShare (assuming the application is installed at songshare.com) We want a URL for a user JohnD to look like this: songshare.com/johnd Because it would be off-topic to make a full application and display the code here, I am going to show a temporary version of our list action. This will raise an exception, passing any supplied parameters. This causes the application to fail and show these parameters in the browser. I won’t go into detail of how to create the search, as it doesn’t pertain to this and the parameters are really all we need for this example.
1 2 3 4 5 class SongController < ApplicationController def list raise params.inspect end end
Now, let’s create our route. Open up config/routes.rb again and add the following line after the routes already provided:
1 2 3 map.connect ':user', :controller => 'song', :action => 'list', :filter => 'user'
If you start your local server running on the default port of 3000, you should now be able to go to http://localhost:3000/johnd and see an error page with the heading RuntimeError in SongController#list. This is a good thing (for our purposes at least). You now know that your page request was routed to the list action previously created. Below the heading, you should see a listing of the request’s parameters. You should see four items here: the user parameter, the action, the filter parameter, and the controller.
That’s all great, but let’s get more specific now. What if we wanted to limit our search to songs that are posted by a specific user and tagged as electro. Let’s modify the previous route declaration to look like the following (NOTE: There are multiple ways to do this, it’s up to you!):
1 2 3 4 map.connect ':user/:tag', :controller => 'song', :action => 'list', :filter => 'user', :tag => ' '
This route will match URLs such as http://localhost:3000/johnd/electro and populate both the :user and :tag parameters. If no tag is specified, the route will still match, and an empty string will be placed in the :tag parameter.
Now let’s make a route to display the top-ranked songs at http://localhost:3000/popular. We will also allow tags to be supplied as we did in the previous example. Add the following to routes.rb:
1 2 3 4 map.connect 'popular/:tag', :controller => 'song', :action => 'list', :tag => '', :filter => 'popular'
This must go above the existing routes in routes.rb or else the wrong parameters will be populated. This is because the Routes engine processes routes in the order they appear in the file and there is nothing to distinguish the word popular from a user’s name.
Finally, let’s allow you to display all the songs that were added on a specific day. Put this route before the user route:
1 2 3 4 5 6 7 8 9 10 11 12 map.connect 'daily/:month/:day/:year', :controller => 'song', :action => 'list', :filter => 'daily', :month => 'Time.now.month', :day => 'Time.now.day', :year => 'Time.now.year', :requirements => { :year => /\d+/, :day => /\d+/, :month => /\d+/ }
This route will accept URLs such as http://localhost:3000/daily/08/18/2008 and populates the :month, :day, and :year parameters. The :requirements parameter we specified that the year, day, and month must be numeric digits for the route to match.
And there you have it, how to make pretty URLs using routes in rails. If anyone has any other methods or questions, please post in the comments.
[ad#adroll-1]