Ryan Bigg

⟵ Posts

Pretty-ifying URLs

18 Apr 2009

In today’s modern world we are not limited (cough, FORTRAN) to storing information in just a couple of bits. Some say that having pretty URLs is good SEO. I say it’s just common sense. I want the URL to tell a story, not give me some number that is only important to the system it’s coming from.

This is where to_param comes in.

By default this is defined like this:

def to_param
  id
end

Pretty high-tech stuff there. I won’t explain it to you. Give it a moment to sink it. et-cetera. This method is called on an object when it is passed to a url helper such as link_to or redirect_to. It is important to note that you should NEVER call @object.id as that will give the id of the object, not the to_param version of that name.

You can override this in your model in order to give you pretty urls! The way you do this is:

def to_param
  "#{id}-#{name.parameterize}"
end

Where id is the id of the model and name is the text you want into the URL. We call parameterize on this text in order to make it URL friendly.

The reason why we put in the id in the URL is two-fold. Firstly, (/forums/1-best-forum-ever) is so that when it gets passed to our controller as params[:id] or similar, we’re able to pass it to a finder:

Forum.find(params[:id])
# will be passed in as...
Forum.find("1-best-forum-ever")

Now you may go “Hey… wait a second! I don’t have an ID in my database that says ‘1-best-forum-ever’!” and you’d be right, you don’t. But you do have an ID of 1 for a record somewhere. Rails will call to_i on this value and convert it simply down to 1 and you’ll be able to treat the pretty URL as if it were a real ID.

Secondly, if two objects in your database have the same name they won’t clash for the parameterized versions, since the id will always be unique.

But I don't want my ID in my URL

Hey, that’s cool too! Just a little tougher… Before you save your records you’ll want to write out the permalink as a new field, so define a field called permalink in your table if you want to take this route. Then you do a before_save and define to_param like this:

before_create :set_permalink

def set_permalink
  self.permalink = name.parameterize
end

def to_param
  permalink
end

Now when you call the finder you’re going to need to find_by_permalink! instead of just find:

Forum.find_by_permalink!(params[:id])

We have to use the bang version of find_by_permalink because this will raise an ActiveRecord::RecordNotFound exception if the record is not found, just like find. (Thanks to Yarsolav Markin for mentioning this)

So there you have it. to_param overriding for pretty URLs.