Friday, January 30, 2009

Nicer URLs in Rails

By default in Rails, a resource has a URL that indicates what kind of resource it is, and what its id number is. So you might have a website with information about bands, and the URL www.music.com/bands/123 might refer to the Rolling Stones.

For lots of reasons it'd be preferable to be able to have www.music.com/bands/the-rolling-stones instead. Here's a straightforward way to do this - but note, as things stand it will break any incoming links you might have.

The basic idea is that rather than retrieving a record based on its id, we retrieve records based on another field that corresponds to the desired URL. We'll call this field munged_name.

So first we need to create the following migration

class AddMungedNameToBands < ActiveRecord::Migration
def self.up
add_column 'bands', 'munged_name', :string
end

def self.down
remove_column 'bands', 'munged_name'
end
end

Then, to the Band model, add

def to_param
name.gsub(/\s/, "-").gsub(/[^\w&-]/,'').downcase
end

With Rails, every object has a to_param method. By default, ActiveRecord objects return the object's id. Here, we override that with a method that returns the band's name, with any spaces replaced with dashes (the first regexp), any punctuation stripped out (the second regexp), and converted to lower case. This is apparently known as munging. So now, applying the to_param method to a Band object returns the munged form of the band's name

Also to the Band model, add

def calculate_munged_name
self.munged_name = to_param
end

This sets the munged_name field and will be called after record has been created.

To the create and update methods of the bands controller, add

@band.calculate_munged_name

just after @band has been set

Given that we already have records in our database we need to give them munged names, so at the console do

>> Band.find(:all).each do |b|
b.calculate_munged_name
b.save
end

Finally, in the show, edit, update, and destroy methods of the bands controller, change

@band = Band.find(params[:id]
to
@band = Band.find_by_munged_name(params[:id])


No comments: